2015-05-20 21 views
5

Ho scritto un programma che ha trait Animal e struct Dog che implementa il tratto e struct AnimalHouse memorizzando un animale come oggetto tratto Box<Animal>.Come clonare una struttura che memorizza un oggetto tratto?

trait Animal{ 
    fn speak(&self); 
} 

struct Dog{ 
    name: String 
} 

impl Dog{ 
    fn new(name: &str) -> Dog { 
     return Dog{name: name.to_string()} 
    } 
} 

impl Animal for Dog{ 
    fn speak(&self){ 
     println!{"{}: ruff, ruff!", self.name}; 
    } 
} 

struct AnimalHouse{ 
    animal: Box<Animal> 
} 

fn main(){ 
    let house = AnimalHouse{animal: Box::new(Dog::new("Bobby"))}; 
    house.animal.speak(); 
} 

Funziona perfettamente e restituisce "Bobby: ruff, ruff!" come previsto.

Ma se provo a clonare house il compilatore restituisce errori

fn main(){ 
    let house = AnimalHouse{animal: Box::new(Dog::new("Bobby"))}; 
    let house2 = house.clone() 
    house2.animal.speak(); 
} 
 
32:31 error: type `AnimalHouse` does not implement any method in scope named `clone` 
    let house2 = house.clone(); 
         ^~~~~~~ 
32:31 help: methods from traits can only be called if the trait is implemented and in scope; the following trait defines a method `clone`, perhaps you need to implement it: 
32:31 help: candidate #1: `core::clone::Clone` 
error: aborting due to previous error 

Ho provato ad aggiungere #[derive(Clone)] prima struct AnimalHouse e ha ottenuto un altro errore:

 
24:24 error: the trait `core::marker::Sized` is not implemented for the type `Animal` [E0277] 
    animal: Box 
        ^~~~~~~~~~~~~~~~~~~ 
22:15 note: in expansion of #[derive_Clone] 
22:15 note: expansion site 
24:24 note: `Animal` does not have a constant size known at compile-time 
    animal: Box 
        ^~~~~~~~~~~~~~~~~~~ 
22:15 note: in expansion of #[derive_Clone] 
22:15 note: expansion site 
24:24 error: the trait `core::clone::Clone` is not implemented for the type `Animal` [E0277] 
    animal: Box 
        ^~~~~~~~~~~~~~~~~~~ 
22:15 note: in expansion of #[derive_Clone] 
22:15 note: expansion site 
error: aborting due to 2 previous errors 

Quindi, come fare lo struct Animal House clonabile? È normale che la ruggine usi attivamente un oggetto tratto (in generale)?

risposta

10

Ci sono alcuni problemi. Il primo è che non c'è nulla da richiedere che un Animal implementa anche Clone. Ora, si potrebbe risolvere il problema cambiando la definizione trait:

trait Animal: Clone { 
    /* ... */ 
} 

Ma questo provoca Animal non essere più oggetto di sicurezza, il che significa che Box<Animal> non sarà più valido. Quindi non è grandioso.

Cosa si può fare è inserire un passaggio aggiuntivo. A favore:

Modifica: Quanto segue è stato modificato in base al commento di @ ChrisMorgan.

trait Animal: AnimalClone { 
    fn speak(&self); 
} 

// Splitting AnimalClone into its own trait allows us to provide a blanket 
// implementation for all compatible types, without having to implement the 
// rest of Animal. In this case, we implement it for all types that have 
// 'static lifetime (*i.e.* they don't contain non-'static pointers), and 
// implement both Animal and Clone. Don't ask me how the compiler resolves 
// implementing AnimalClone for Animal when Animal requires AnimalClone; I 
// have *no* idea why this works. 
trait AnimalClone { 
    fn clone_box(&self) -> Box<Animal>; 
} 

impl<T> AnimalClone for T where T: 'static + Animal + Clone { 
    fn clone_box(&self) -> Box<Animal> { 
     Box::new(self.clone()) 
    } 
} 

// We can now implement Clone manually by forwarding to clone_box. 
impl Clone for Box<Animal> { 
    fn clone(&self) -> Box<Animal> { 
     self.clone_box() 
    } 
} 

#[derive(Clone)] 
struct Dog { 
    name: String, 
} 

impl Dog { 
    fn new(name: &str) -> Dog { 
     return Dog { name: name.to_string() } 
    } 
} 

impl Animal for Dog { 
    fn speak(&self) { 
     println!("{}: ruff, ruff!", self.name); 
    } 
} 

#[derive(Clone)] 
struct AnimalHouse { 
    animal: Box<Animal>, 
} 

fn main() { 
    let house = AnimalHouse { animal: Box::new(Dog::new("Bobby")) }; 
    let house2 = house.clone(); 
    house2.animal.speak(); 
} 

Con l'introduzione di clone_box, siamo in grado di aggirare i problemi con il tentativo di clonare un oggetto tratto.

+0

Mettere 'clone_box' come un metodo sul tratto stesso è piuttosto inefficiente, richiedendo a tutti gli implementatori di implementarlo nello stesso modo. Una soluzione migliore è quella di avere una supertrait di 'Animal' con un'implementazione coperta per' T: Animal + Clone'. * Questo * è l'approccio utilizzato in cose come AnyMap. –

+0

@ChrisMorgan: buona idea; cambiato. –