HiveBrain v1.2.0
Get Started
← Back to all entries
snippetrustCritical

How to clone a struct storing a boxed trait object?

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
objecthowstructboxedclonestoringtrait

Problem

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.

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,
}

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,
| ^^^^^^^^^^^^^^^^^^^ 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
= 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 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;
}

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

// We can now implement Clone manually by forwarding to clone_box.
impl Clone for Box {
fn clone(&self) -> Box {
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,
}

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.

Context

Stack Overflow Q#30353462, score: 108

Revisions (0)

No revisions yet.