简体   繁体   English

如何克隆存储装箱特征对象的结构?

[英]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.我编写了一个程序,该程序具有 trait Animal和实现该 trait 的结构Dog It also has a struct AnimalHouse storing an animal as a trait object Box<Animal> .它还有一个 struct AnimalHouse将动物存储为特征对象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!"它返回“Bobby:ruff,ruff!” as expected, but if I try to clone house the compiler returns errors:正如预期的那样,但如果我尝试克隆house ,编译器会返回错误:

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:我试图在struct AnimalHouse之前添加#[derive(Clone)]并得到另一个错误:

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?如何使结构体AnimalHouse Is it idiomatic Rust to use a trait object actively, in general?一般来说,主动使用特征对象是惯用的 Rust 吗?

There are a few problems.有几个问题。 The first is that there's nothing to require that an Animal also implements Clone .首先是没有什么要求Animal也实现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.这将导致Animal不再是对象安全的,这意味着Box<dyn Animal>将变得无效,所以这不是很好。

What you can do is insert an additional step.可以做的是插入一个额外的步骤。 To whit (with additions from @ChrisMorgan's comment ). 顺便说一句(加上@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<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.通过引入clone_box ,我们可以解决试图克隆 trait 对象的问题。

My dyn-clone crate implements a reusable version of DK.'s answer .我的dyn-clone crate 实现了DK.'s answer的可重用版本。 With it you can make your original code work with a bare minimum of changes.有了它,您只需进行最少的更改即可使您的原始代码正常工作。

  • One line to add DynClone as a supertrait of Animal , requiring every animal implementation to be clonable.一行添加DynClone作为Animal ,要求每个动物实现都是可克隆的。
  • One line to generate an implementation of the standard library Clone for Box<dyn Animal> .一行生成Box<dyn Animal>的标准库Clone实现。

// [dependencies]
// dyn-clone = "1.0"

use dyn_clone::{clone_trait_object, DynClone};

trait Animal: DynClone {
    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<dyn Animal>,
}

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

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:关于标题,但不是关于使用 trait 对象的惯用方式,另一种解决方案是使用Rc智能指针而不是Box :这避免了绕过对象安全的解决方法:

#[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;注意Rc<T>仅用于单线程场景; there's also Arc<T> .还有Arc<T>

I've tried to use the solution from Dk as well as dtolnay in a situation where I need a struct with a member with box in a spawned task (via tokio).我尝试使用来自 Dk 和 dtolnay 的解决方案,在这种情况下,我需要一个带有成员的结构体,在生成的任务中(通过 tokio)。 There I get errors that the struct is not send and sync.在那里我收到结构未发送和同步的错误。 To avoid this, one could add send and sync in Dk clone traits.为了避免这种情况,可以在 Dk 克隆特征中添加发送和同步。 Maybe this could also be added to dyn_clone.也许这也可以添加到 dyn_clone。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM