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

Posted

技术标签:

【中文标题】如何克隆存储盒装特征对象的结构?【英文标题】:How to clone a struct storing a boxed trait object? 【发布时间】:2015-08-01 22:11:04 【问题描述】:

我编写了一个程序,该程序具有 Animal 特征和实现该特征的结构 Dog。它还有一个结构体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();

它返回“鲍比:拉夫,拉夫!”正如预期的那样,但如果我尝试克隆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`

我尝试在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`

如何使结构AnimalHouse 可克隆?一般来说,积极使用 trait 对象是惯用的 Rust 吗?

【问题讨论】:

【参考方案1】:

有几个问题。首先是没有什么要求Animal 也实现Clone。您可以通过更改特征定义来解决此问题:

trait Animal: Clone 
    /* ... */

这将导致 Animal 不再是对象安全的,这意味着 Box&lt;dyn Animal&gt; 将变得无效,所以这不是很好。

可以做的是插入一个额外的步骤。 To whit(来自@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<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();

通过引入clone_box,我们可以解决试图克隆特征对象的问题。

【讨论】:

不确定我是否应该提出另一个问题,但为什么扩展Clone 意味着您的特征不再是对象安全的?这是什么意思? @Oli clone 返回Self。这对于 trait 对象是不允许的,因为在编译时无法知道大小。 愚蠢的问题:如何为生命周期有限(非静态)的类型实现这一点?【参考方案2】:

我的dyn-clone crate 实现了DK.'s answer 的可重用版本。有了它,您只需进行最少的更改即可使您的原始代码正常工作。

一行添加DynClone 作为Animal 的超特征,要求每个动物实现都是可克隆的。 一行代码为Box&lt;dyn Animal&gt; 生成标准库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();

【讨论】:

【参考方案3】:

previous answer 正确回答了有关存储盒装特征对象的问题。

关于标题的话题,但不是关于使用 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();

注意Rc&lt;T&gt;仅用于单线程场景;还有Arc&lt;T&gt;

【讨论】:

我认为重要的是要注意这是一个可行的替代方案当且仅当业务逻辑不需要对象的真正克隆,即引用不同的内存中的对象的副本。 Rc 只是克隆指针,而不是数据本身。 另外值得注意的是,如果数据被借用为可变数据,这是不可接受的,因为您不能将 Rc 借用为可变数据。【参考方案4】:

我尝试使用 Dk 和 dtolnay 中的解决方案,在这样的情况下,我需要一个结构,其中包含一个带有框的成员在一个衍生任务中(通过 tokio)。在那里我收到结构未发送和同步的错误。为了避免这种情况,可以在 Dk 克隆特征中添加发送和同步。也许这也可以添加到 dyn_clone 中。

【讨论】:

以上是关于如何克隆存储盒装特征对象的结构?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用复制或克隆的情况下克隆Rust中的结构?

JavaSE基础面试总结

JavaSE基础面试总结

如何将 CoreData SQLite 支持的持久存储克隆到“内存中”?

如何制作俄罗斯方块克隆?

在 JavaScript 中克隆对象