Considera el siguiente código:
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 = Vec::new(); v.push(cat); v.push(dog); for animal in v.iter() { println!("{}", animal.make_sound()); } }
El comstackdor me dice que v
es un vector de Animal
cuando bash empujar a cat
(no coincide con el tipo)
Entonces, ¿cómo puedo hacer un vector de objetos que pertenecen a un rasgo y llamar al método de rasgo correspondiente en cada elemento?
Vec
no es legal, pero el comstackdor no puede decirte eso porque el tipo no coincide de alguna manera lo oculta. Si eliminamos las llamadas para push
, el comstackdor nos da el siguiente error:
:22:9: 22:40 error: instantiating a type parameter with an incompatible type `Animal`, which does not fulfill `Sized` [E0144] :22 let mut v: Vec = Vec::new(); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
La razón por la que eso no es legal es que un Vec
almacena muchos objetos T
consecutivamente en la memoria. Sin embargo, Animal
es un rasgo, y los rasgos no tienen tamaño (no se garantiza que un Cat
y un Dog
tengan el mismo tamaño).
Para resolver este problema, debemos almacenar algo que tenga un tamaño en el Vec
. La solución más directa es envolver los valores en un Box
, es decir, Vec
. Box
tiene un tamaño fijo (un “puntero gordo” si T es un rasgo, un puntero simple en caso contrario).
Aquí hay un trabajo main
:
fn main () { let dog: Dog = Dog; let cat: Cat = Cat; let mut v: Vec> = Vec::new(); v.push(Box::new(cat)); v.push(Box::new(dog)); for animal in v.iter() { println!("{}", animal.make_sound()); } }
Puede usar un objeto de rasgo de referencia &Animal
para tomar prestados los elementos y almacenar estos objetos de rasgo en un Vec
. Luego puede enumerarlo y usar la interfaz del rasgo.
Alterar el tipo genérico de Vec
añadiendo un &
delante del rasgo funcionará:
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()); }
Esto es genial si desea que la variable original mantenga la propiedad y la reutilice más adelante.
Tenga en cuenta que con el escenario anterior, no puede transferir la propiedad de un dog
o un cat
porque el Vec
ha tomado prestadas estas instancias concretas en el mismo ámbito.
La introducción de un nuevo scope puede ayudar a manejar esa situación particular:
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()); }