2014-09-12 18 views
36

Si consideri il seguente codice:vettoriali di oggetti appartenenti a un tratto

trait Animal { 
    fn make_sound(&self) -> String; 
} 

struct Cat; 
impl Animal for Cat { 
    fn make_sound(&self) -> String { 
     "meow".to_string() 
    } 
} 

struct Dog; 
impl Animal for Dog { 
    fn make_sound(&self) -> String { 
     "woof".to_string() 
    } 
} 

fn main() { 
    let dog: Dog = Dog; 
    let cat: Cat = Cat; 
    let v: Vec<Animal> = Vec::new(); 
    v.push(cat); 
    v.push(dog); 
    for animal in v.iter() { 
     println!("{}", animal.make_sound()); 
    } 
} 

Il compilatore mi dice che v è un vettore di Animal quando provo a spingere cat (tipo non corrispondente)

Quindi, come posso creare un vettore di oggetti appartenenti a un tratto e chiama il metodo tratto corrispondente su ciascun elemento?

risposta

48

Vec<Animal> non è legale, ma il compilatore non è in grado di dirlo perché il tipo non corrispondente lo nasconde in qualche modo. Se togliamo le chiamate al push, il compilatore ci dà il seguente errore:

<anon>:22:9: 22:40 error: instantiating a type parameter with an incompatible type `Animal`, which does not fulfill `Sized` [E0144] 
<anon>:22  let mut v: Vec<Animal> = Vec::new(); 
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

Il motivo per cui questo non è legale è che un Vec<T> memorizza molti T oggetti consecutivamente in memoria. Tuttavia, Animal è un tratto e i tratti non hanno dimensioni (a Cat e a Dog non è garantito che abbiano le stesse dimensioni).

Per risolvere questo problema, è necessario memorizzare qualcosa che ha una dimensione nello Vec. La soluzione più semplice consiste nel racchiudere i valori in un Box, ad esempio Vec<Box<Animal>>. Box<T> ha una dimensione fissa (un "fat pointer" se T è un tratto, un puntatore semplice altrimenti).

Ecco un main di lavoro:

fn main() { 
    let dog: Dog = Dog; 
    let cat: Cat = Cat; 
    let mut v: Vec<Box<Animal>> = Vec::new(); 
    v.push(Box::new(cat)); 
    v.push(Box::new(dog)); 
    for animal in v.iter() { 
     println!("{}", animal.make_sound()); 
    } 
} 
+0

Ah capisco. Ha senso che i vettori necessitino di tipi di dimensioni deterministiche. Grazie! –

7

è possibile utilizzare un tratto oggetto di riferimento &Animal di prendere in prestito gli elementi e memorizzare questi oggetti di carattere in un Vec. È quindi possibile enumerarlo e utilizzare l'interfaccia del carattere.

Alterare tipo generico 's il Vec aggiungendo un & davanti al tratto funzionerà:

fn main() { 
    let dog: Dog = Dog; 
    let cat: Cat = Cat; 
    let mut v: Vec<&Animal> = Vec::new(); 
    //    ~~~~~~~ 
    v.push(&dog); 
    v.push(&cat); 
    for animal in v.iter() { 
     println!("{}", animal.make_sound()); 
    } 
    // Ownership is still bound to the original variable. 
    println!("{}", cat.make_sound()); 
} 

Questo è grande se si consiglia la variabile originale per mantenere la proprietà e riutilizzare in un secondo momento.

tenere a mente con lo scenario di cui sopra, non è possibile trasferire la proprietà di dog o cat perché il Vec ha preso in prestito questi casi concreti allo stesso ambito.

L'introduzione di un nuovo ambito può aiutare a gestire quella particolare situazione:

fn main() { 
    let dog: Dog = Dog; 
    let cat: Cat = Cat; 
    { 
     let mut v: Vec<&Animal> = Vec::new(); 
     v.push(&dog); 
     v.push(&cat); 
     for animal in v.iter() { 
      println!("{}", animal.make_sound()); 
     } 
    } 
    let pete_dog: Dog = dog; 
    println!("{}", pete_dog.make_sound()); 
} 
Problemi correlati