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

问题描述 投票:0回答:3

我编写了一个具有特征

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

它返回“Bobby:ruff,ruff!”正如预期的那样,但是如果我尝试克隆

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`

我尝试在

#[derive(Clone)]
之前添加
struct AnimalHouse
并收到另一个错误:

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 惯用的做法吗?

struct clone rust traits cloneable
3个回答
93
投票

有一些问题。首先,不需要

Animal
也实现
Clone
。您可以通过更改特征定义来解决此问题:

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

这会导致

Animal
不再是对象安全的,这意味着
Box<dyn Animal>
将变得无效,所以这不太好。

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

通过引入

clone_box
,我们可以解决尝试克隆特征对象时遇到的问题。


38
投票

我的

dyn-clone
板条箱实现了DK的答案的可重用版本。有了它,您只需进行最少的更改即可使原始代码正常工作。

  • 一行添加
    DynClone
    作为
    Animal
    的超级特征,要求每个动物实现都是可克隆的。
  • 一行即可生成标准库的实现
    Clone
    for
    Box<dyn Animal>

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

19
投票

编辑(dric512):稍微更新(修复)这个答案。它部分回答了问题,但仍然是一个有用的答案,但有局限性:

  • 克隆父结构时,不会克隆动态特征,仅增加引用计数。如果需要复制动态对象,则不能使用此功能。
  • 因此,该特质不需要扩展
    Clone
    并成为对象安全。

之前的答案正确回答了有关存储盒装特征对象的问题。

偏离主题,但不是关于使用特征对象的惯用方式,替代解决方案可以使用

Rc
智能指针而不是
Box
:特征对象不需要实现
Clone 
因为克隆结构意味着增加对象的引用计数:

trait Animal {
    fn speak(&self);
}

...

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

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

注意

Rc<T>
仅适用于单线程场景;还有
Arc<T>

© www.soinside.com 2019 - 2024. All rights reserved.