structclonerusttraitscloneable

How to clone a struct storing a boxed trait object?


I wrote a program that has the trait Animal and the struct Dog implementing the trait. It also has a struct AnimalHouse storing an animal as a trait object Box<Animal>.

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();
}

It returns "Bobby: ruff, ruff!" as expected, but if I try to clone house the compiler returns errors:

fn main() {
    let house = AnimalHouse {
        animal: Box::new(Dog::new("Bobby")),
    };
    let house2 = house.clone();
    house2.animal.speak();
}
error[E0599]: no method named `clone` found for type `AnimalHouse` in the current scope
  --> src/main.rs:31:24
   |
23 | struct AnimalHouse {
   | ------------------ method `clone` not found for this
...
31 |     let house2 = house.clone();
   |                        ^^^^^
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `clone`, perhaps you need to implement it:
           candidate #1: `std::clone::Clone`

I tried to add #[derive(Clone)] before struct AnimalHouse and got another error:

error[E0277]: the trait bound `Animal: std::clone::Clone` is not satisfied
  --> src/main.rs:25:5
   |
25 |     animal: Box<Animal>,
   |     ^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `Animal`
   |
   = note: required because of the requirements on the impl of `std::clone::Clone` for `std::boxed::Box<Animal>`
   = note: required by `std::clone::Clone::clone`

How do I make the struct AnimalHouse cloneable? Is it idiomatic Rust to use a trait object actively, in general?


Solution

  • There are a few problems. The first is that there's nothing to require that an Animal also implements Clone. You could fix this by changing the trait definition:

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

    This would cause Animal to no longer be object safe, meaning that Box<dyn Animal> will become invalid, so that's not great.

    What you can do is insert an additional step. To whit (with additions from @ChrisMorgan's comment).

    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 dyn Animal when Animal requires AnimalClone;
    // I have *no* idea why this works.
    trait AnimalClone {
        fn clone_box(&self) -> Box<dyn Animal>;
    }
    
    impl<T> AnimalClone for T
    where
        T: 'static + Animal + Clone,
    {
        fn clone_box(&self) -> Box<dyn Animal> {
            Box::new(self.clone())
        }
    }
    
    // We can now implement Clone manually by forwarding to clone_box.
    impl Clone for Box<dyn Animal> {
        fn clone(&self) -> Box<dyn Animal> {
            self.clone_box()
        }
    }
    
    #[derive(Clone)]
    struct Dog {
        name: String,
    }
    
    impl Dog {
        fn new(name: &str) -> Dog {
            Dog {
                name: name.to_string(),
            }
        }
    }
    
    impl Animal for Dog {
        fn speak(&self) {
            println!("{}: ruff, ruff!", self.name);
        }
    }
    
    #[derive(Clone)]
    struct AnimalHouse {
        animal: Box<dyn Animal>,
    }
    
    fn main() {
        let house = AnimalHouse {
            animal: Box::new(Dog::new("Bobby")),
        };
        let house2 = house.clone();
        house2.animal.speak();
    }
    

    By introducing clone_box, we can get around the problems with attempting to clone a trait object.