无法克隆Vec<Box<Trait>>,因为Trait不能成为一个对象。

12

我正在尝试克隆一个装箱特质向量。显然,仅在实现我的特质的所有结构体上简单地派生 Clone 是不够的,因为编译器在编译时不知道实现该特质的所有结构体都有 Clone

好吧,然后我试图将 Clone 用作超特质,但这只会导致标题中的错误。我对解决方案感到困惑。

以下是最小工作实现(或不起作用,因为我无法进行克隆)

```rust // Rust code ```

#![allow(dead_code, unused_macros)]
use std::fmt::Debug;

trait MusicElement: Debug + Clone {
    fn duration(&self) -> f32;
}

#[derive(Debug, Clone)]
struct Note<'a> {
    name: &'a str,
    duration: f32,
}

impl<'a> MusicElement for Note<'a> {
    fn duration(&self) -> f32 {
        self.duration
    }
}

#[derive(Debug, Clone)]
struct Pause {
    duration: f32,
}

impl MusicElement for Pause {
    fn duration(&self) -> f32 {
        self.duration
    }
}

#[derive(Debug, Clone)]
struct Sequence {
    elements: Vec<Box<MusicElement>>,
}

impl MusicElement for Sequence {
    fn duration(&self) -> f32 {
        self.elements.iter().map(|e| e.duration()).sum()
    }
}

fn main() {
    let a4 = |dur| Box::new(Note { name: "a4", duration: dur });
    let seq = Sequence { elements: vec![a4(0.25), a4(0.25), a4(0.5)] };
    println!("{:?}", seq);
    let seq2 = seq.clone();
    println!("{:?}", seq2);
}

出现了这个错误:

error[E0038]: the trait `MusicElement` cannot be made into an object
  --> src/main.rs:33:5
   |
33 |     elements: Vec<Box<MusicElement>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `MusicElement` cannot be made into an object
   |
   = note: the trait cannot require that `Self : Sized`

这里有一个游乐场链接,方便运行代码。

我还尝试将Sequence中的elements向量改为Vec<Box<MusicElement + Clone>>,但那也没有起作用。

我在网上找不到任何有用的解决方案,所以我的问题是:如何使代码可克隆?


可能是重复:如何克隆存储箱化特征对象的结构体?。@Electric Coffee:这能帮到您吗? - Lukas Kalbertodt
我收到一个错误,说Note<'a>不满足静态生命周期。 - Electric Coffee
1
为什么不使用String作为名称? - Boiethios
你的业务逻辑真的需要实现Clone吗?这个错误是由于Rust编译器在编译时需要知道Clone::clone的具体返回类型,而使用动态分发的Trait对象Box<MusicElement>是不可能实现的。点击此处了解更多关于对象安全性违规的信息 - jonny
1
不幸的是,是的。Sequence结构体允许我创建深度嵌套的MusicElements链,程序需要能够处理它们。为了保持自己的理智,我更喜欢复制这样一个序列,而不是被迫从头开始编写它。 - Electric Coffee
2个回答

11
解决方案在于结合目前为止评论中的建议 - @Lukas Kalbertodt的评论中的答案告诉你必须为所有兼容的('static + MusicElement + Clone)类型创建一个全局trait实现。对于你的实现,唯一需要的后续步骤是将Note.name字段的类型从&'a str更改为String,如Boiethios所提到的
#![allow(dead_code, unused_macros)]
use std::fmt::Debug;

trait MusicElement: MusicElementClone + Debug {
    fn duration(&self) -> f32;
}

trait MusicElementClone {
    fn clone_box(&self) -> Box<MusicElement>;
}

impl<T: 'static + MusicElement + Clone> MusicElementClone for T {
    fn clone_box(&self) -> Box<MusicElement> {
        Box::new(self.clone())
    }
}

impl Clone for Box<MusicElement> {
    fn clone(&self) -> Box<MusicElement> {
        self.clone_box()
    }
}

#[derive(Debug, Clone)]
struct Note {
    name: String,
    duration: f32,
}

impl MusicElement for Note {
    fn duration(&self) -> f32 {
        self.duration
    }
}

#[derive(Debug, Clone)]
struct Pause {
    duration: f32,
}

impl MusicElement for Pause {
    fn duration(&self) -> f32 {
        self.duration
    }
}

#[derive(Debug, Clone)]
struct Sequence {
    elements: Vec<Box<MusicElement>>,
}

impl MusicElement for Sequence {
    fn duration(&self) -> f32 {
        self.elements.iter().map(|e| e.duration()).sum()
    }
}

fn main() {
    let a4 = |dur| Box::new(Note { name: String::from("a4"), duration: dur });
    let seq = Sequence { elements: vec![a4(0.25), a4(0.25), a4(0.5)] };
    println!("{:?}", seq);
    let seq2 = seq.clone();
    println!("{:?}", seq2);
}

这个可以编译通过,所以应该足够了!


4
我的 crate dyn-clone 提供了一个可重用的实现,jonny 的回答。使用它,您可以仅通过最少的更改使原始代码正常运行。

之前:

trait MusicElement: Debug + Clone {
    fn duration(&self) -> f32;
}

完成后:

use dyn_clone::DynClone;

trait MusicElement: Debug + DynClone {
    fn duration(&self) -> f32;
}

dyn_clone::clone_trait_object!(MusicElement);

// Everything else as you wrote it.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接