How to clone a struct storing a boxed trait object?

孤街醉人 提交于 2019-11-26 17:50:19
DK.

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

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

My objekt crate implements a reusable version of DK.'s answer. With it you can make your original code work with a bare minimum of changes.

  • One line to import the crate.
  • One line to add objekt::Clone as a supertrait of Animal, requiring every animal implementation to be clonable.
  • One line to generate an implementation of the standard library Clone for Box<Animal>.

#[macro_use] extern crate objekt;

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

clone_trait_object!(Animal);

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

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

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

The previous answer correctly answers the question about storing a boxed trait object.

Getting off topic with respect to the title, but not about the idiomatic way of using trait objects, an alternative solution could be use the Rc smart pointer instead of a Box: this avoids the workaround for getting around object safety:

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

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

Note: Rc<T> is only for use in single-threaded scenarios; there's also Arc<T>.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!