我如何克隆一个 Vec<Box<dyn Trait>>?

6

我想实现一个函数,接受一个不可变的&Vec引用,制作一份副本,对值进行排序并打印。

这是主要代码。

trait Foo {
    fn value(&self) -> i32;
}

struct Bar {
    x: i32,
}

impl Foo for Bar {
    fn value(&self) -> i32 {
        self.x
    }
}

fn main() {
    let mut a: Vec<Box<dyn Foo>> = Vec::new();
    a.push(Box::new(Bar { x: 3 }));
    a.push(Box::new(Bar { x: 5 }));
    a.push(Box::new(Bar { x: 4 }));

    let b = &a;
    sort_and_print(&b);
}

我唯一能使其工作的方法是这样的

fn sort_and_print(v: &Vec<Box<dyn Foo>>) {
    let mut v_copy = Vec::new();
    for val in v {
        v_copy.push(val);
    }
    v_copy.sort_by_key(|o| o.value());
    for val in v_copy {
        println!("{}", val.value());
    }
}

然而我想理解这里发生了什么,同时也想让代码更短。

问题1

如果我尝试将 let mut v_copy = Vec::new(); 更改为 let mut v_copy: Vec<Box<dyn Foo>> = Vec::new();,会导致各种错误,我不知道该如何修复。

如何使这个版本工作,并且它与第一个版本有何不同?

尝试2

更接近我想要的是这样的代码:let mut v_copy = v.clone(); 但这并不能起作用。能否修复这个版本?


1
你可以使用 let mut v_copy: Vec<_> = v.iter().collect() 来缩短复制的过程。更好的问题是,当你只在 Vec 中放置一个类型时,为什么需要动态分派?这将摆脱盒子。你也可以给 sort_and_print 一个获取值的闭包,并完全摆脱特质:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0657ffcad6e4a78c1b73917d4f7f22c4 - user4815162342
我已经发布了简化后的代码。实际代码使用不同的结构体,它们都实现了“Foo”特性。感谢您提供的代码,它有效!所以我们实际上创建了一个Vec<&Box<dyn Foo>>而不是Vec<Box<dyn Foo>>,但这不是问题。这也回答了问题1。那么,在这些情况下,.iter().collect()是标准方法吗? - George Marcus
是的,你的代码一直在创建一个 Vec<&Box<dyn Foo>>,而 .iter().collect() 只是更简洁的做法。let x: Vec<_> = <some iterator>.collect() 是创建一个你要迭代的向量的标准 Rust 短语。 - user4815162342
2个回答

10
首先,让我们对类型进行注释:
fn sort_and_print(v: &Vec<Box<dyn Foo>>) {
    let mut v_copy: Vec<&Box<dyn Foo>> = Vec::new();
    for val /* : &Box<dyn Foo> */ in v {
        v_copy.push(val);
    }
    v_copy.sort_by_key(|o: &&Box<dyn Foo>| <dyn Foo>::value(&***o));
    for val /* : &Box<dyn Foo> */ in v_copy {
        println!("{}", <dyn Foo>::value(&**val));
    }
}

遍历 &Vec<T> 会产生一个迭代器,该迭代器遍历 &T(与 .iter() 方法相同)。
现在我们可以看到,我们可以通过在 v 上调用 .into_iter(),然后调用 .collect() 将其转换为迭代器(这就是 for 循环所做的),或者将 into_iter() 替换为 iter()(由于我们正在遍历引用,因此更符合惯例)。
fn sort_and_print(v: &Vec<Box<dyn Foo>>) {
    let mut v_copy: Vec<&Box<dyn Foo>> = v.iter().collect(); // You can omit the `&Box<dyn Foo>` and replace it with `_`, I put it here for clarity.
    v_copy.sort_by_key(|o| o.value());
    for val in v_copy {
        println!("{}", val.value());
    }
}

然而,我们仍然只有参考的副本(&Box<dyn Foo>)。为什么我们不能克隆向量呢?
如果我们尝试...
fn sort_and_print(v: &Vec<Box<dyn Foo>>) {
    let mut v_copy = v.clone();
    v_copy.sort_by_key(|o| o.value());
    for val in v_copy {
        println!("{}", val.value());
    }
}

"...编译器对我们喊道:"
warning: variable does not need to be mutable
  --> src/main.rs:45:9
   |
45 |     let mut v_copy = v.clone();
   |         ----^^^^^^
   |         |
   |         help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

error[E0596]: cannot borrow `*v_copy` as mutable, as it is behind a `&` reference
  --> src/main.rs:46:5
   |
45 |     let mut v_copy = v.clone();
   |         ---------- help: consider changing this to be a mutable reference: `&mut Vec<Box<dyn Foo>>`
46 |     v_copy.sort_by_key(|o| o.value());
   |     ^^^^^^ `v_copy` is a `&` reference, so the data it refers to cannot be borrowed as mutable

“什么????”
“好吧,让我们尝试指定类型。这样可以使编译器更加智能。”
fn sort_and_print(v: &Vec<Box<dyn Foo>>) {
    let mut v_copy: Vec<Box<dyn Foo>> = v.clone();
    v_copy.sort_by_key(|o| o.value());
    for val in v_copy {
        println!("{}", val.value());
    }
}

“不是。”
error[E0308]: mismatched types
  --> src/main.rs:45:41
   |
45 |     let mut v_copy: Vec<Box<dyn Foo>> = v.clone();
   |                     -----------------   ^^^^^^^^^
   |                     |                   |
   |                     |                   expected struct `Vec`, found reference
   |                     |                   help: try using a conversion method: `v.to_vec()`
   |                     expected due to this
   |
   = note: expected struct `Vec<Box<dyn Foo>>`
           found reference `&Vec<Box<dyn Foo>>`

好的,让我们使用编译器的建议:
fn sort_and_print(v: &Vec<Box<dyn Foo>>) {
    let mut v_copy: Vec<Box<dyn Foo>> = v.to_vec();
    v_copy.sort_by_key(|o| o.value());
    for val in v_copy {
        println!("{}", val.value());
    }
}

Grrr!!

error[E0277]: the trait bound `dyn Foo: Clone` is not satisfied
  --> src/main.rs:45:43
   |
45 |     let mut v_copy: Vec<Box<dyn Foo>> = v.to_vec();
   |                                           ^^^^^^ the trait `Clone` is not implemented for `dyn Foo`
   |
   = note: required because of the requirements on the impl of `Clone` for `Box<dyn Foo>`

至少现在我们有了一些线索。
这里发生了什么?
嗯,就像编译器所说的那样,dyn Foo没有实现Clone特性。这意味着Box<dyn Foo>Vec<Box<dyn Foo>>也都没有实现Clone
然而,&Vec<Box<dyn Foo>>实际上是实现了Clone的。这是因为你可以拥有任意数量的共享引用 - 共享(非可变)引用是Copy的,并且每个Copy也是Clone。试一下:
fn main() {
    let i: i32 = 123;
    let r0: &i32 = &i;
    let r1: &i32 = <&i32 as Clone>::clone(&r0);
}

因此,当我们写 v.clone() 时,编译器会询问:“是否有一个名为 clone() 的方法,它以类型为 &Vec<Box<dyn Foo>>v)的 self 作为参数?” 它首先在 Vec<Box<dyn Foo>> 上寻找这样的方法的实现(因为 Clone::clone() 接受 &self,所以对于 Vec<Box<dyn Foo>>,它接受 &Vec<Box<dyn Foo>>)。不幸的是,这样的实现不存在,因此它执行了自动引用(Rust 中尝试调整方法接收者的过程的一部分,您可以在 这里 阅读更多信息),并针对 &&Vec<Box<dyn Foo>> 提出了同样的问题。现在我们找到了匹配项 - <&Vec<Box<dyn Foo>> as Clone>::clone()!这就是编译器调用的内容。
这段文字讲解了一个方法的返回类型是什么,它是 `&Vec<Box<dyn Foo>>`。这也是变量 `v_copy` 的类型。现在我们明白了为什么当我们指定另一种类型时,编译器会出错!我们还可以解密当我们没有指定类型时的错误信息:我们要求编译器在 `&Vec<Box<dyn Foo>>` 上调用 `sort_by_key()` 方法,但该方法需要一个 &mut Vec<Box<dyn Foo>>(确切地说是 &mut [Box<dyn Foo>],但不重要,因为 Vec<T> 可以强制转换为 [T])!

我们还可以理解有关冗余 mut 的警告:我们从未更改引用,因此不需要将其声明为可变的!

当我们调用to_vec()时,编译器并没有混淆:{{to_vec()}}要求向量的项实现{{Clone}}({{where T: Clone}}),而对于{{Box<dyn Foo>}},这并没有发生。崩溃。
现在解决方案应该很清楚了:我们只需要{{Box<dyn Foo>}}实现{{Clone}}。
只是?
我们可能会想到的第一件事是给{{Foo}}一个超级特征{{Clone}}:
trait Foo: Clone {
    fn value(&self) -> i32;
}

#[derive(Clone)]
struct Bar { /* ... */ }

不起作用:
error[E0038]: the trait `Foo` cannot be made into an object
  --> src/main.rs:33:31
   |
33 | fn sort_and_print(v: &Vec<Box<dyn Foo>>) {
   |                               ^^^^^^^ `Foo` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/main.rs:1:12
   |
1  | trait Foo: Clone {
   |       ---  ^^^^^ ...because it requires `Self: Sized`
   |       |
   |       this trait cannot be made into an object...

嗯,看起来 Clone确实需要Sized。为什么?
嗯,因为为了克隆某个东西,我们需要返回它本身。我们可以返回dyn Foo吗?不行,因为它的大小可以是任意的。
所以,让我们尝试手动实现Box<dyn Foo>impl Clone(即使Box没有在我们的crate中定义,因为它是一个基本类型,而Foo是本地定义的(定义在我们的crate中))。
impl Clone for Box<dyn Foo> {
    fn clone(self: &Box<dyn Foo>) -> Box<dyn Foo> {
        // Now... What, actually?
    }
}

“我们怎么能克隆一个可以是任何东西的东西呢?”显然,我们需要将其转发给其他人。还有谁?那些知道如何克隆这个东西的人。在Foo中定义一个方法吗?
trait Foo {
    fn value(&self) -> i32;
    fn clone_dyn(&self) -> Box<dyn Foo>;
}

impl Foo for Bar {
    fn value(&self) -> i32 {
        self.x
    }
    
    fn clone_dyn(&self) -> Box<dyn Foo> {
        Box::new(self.clone()) // Forward to the derive(Clone) impl
    }
}

现在!
impl Clone for Box<dyn Foo> {
    fn clone(&self) -> Self {
        self.clone_dyn()
    }
}

它工作了!
fn sort_and_print(v: &Vec<Box<dyn Foo>>) {
    let mut v_copy = v.clone();
    v_copy.sort_by_key(|o| o.value());
    for val in v_copy {
        println!("{}", val.value());
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d6e871711146bc3f34d9710211b4a1dd

注意:@dtonlay 的 dyn-clone crate 推广了这个想法。

1
即使在我们的板条箱中未定义Box,我们仍然可以做到这一点,因为它是一种基本类型——这并不完全正确。之所以允许这样做,是因为Foo在我们的板条箱中定义,因此在上游板条箱中不可能有任何冲突的实现。 - Sven Marnach
1
当然,但是如果Box不是基础的话,那么Box<dyn Foo>就不是本地的了,我们也无法为其实现外部特征(Clone)。以这种方式,Box是特殊的。编辑回答以澄清这一点。 - Chayim Friedman
是的,抱歉,我有些混淆了。你是完全正确的。实际上,标准库理论上“可能”会为 T: ?Sized 添加一个通用实现,这将与此处的实现冲突,但由于 Box#[fundamental],我们仍然可以添加此实现。 - Sven Marnach
@SvenMarnach 标准库不允许定义这样的实现,因为它会破坏我们的代码;事实上,基本类型的定义是,如果“在其上实现一个全局实现是一种破坏性的变更”,则该类型是基本类型。 - Chayim Friedman

1
你可以使用 Iterator::collect() 使得 sort_and_print() 更短:
fn sort_and_print(v: &[Box<dyn Foo>]) {
    let mut v_copy: Vec<_> = v.iter().collect();
    v_copy.sort_by_key(|o| o.value());
    for val in v_copy {
        println!("{}", val.value());
    }
}

代码演示

顺便提一下,通常更好的方法是通过接受切片来接受向量的引用,如此解释,因此上述答案接受了一个切片。

您可以使用 itertools 包中的 sorted() 方法或本例中的其它替代方法 sorted_by_key() ,使其更简短:

use itertools::Itertools;

fn sort_and_print(v: &[Box<dyn Foo>]) {
    for val in v.iter().sorted_by_key(|o| o.value()) {
        println!("{}", val.value());
    }
}

您几乎肯定不想克隆向量,因为这将涉及深度复制,即克隆每个Box<dyn Foo>,这是不必要的、可能代价高昂的,而且很复杂(如其他答案所述)。


更短的形式是 Vec::from_iter(v)。在 Rust 2021 中,这已经在预导入模块中,所以我将在未来经常使用它。 - Sven Marnach
有趣的是,文档指出 from_iter() 很少被显式调用,这使得它听起来像 .collect() 是使用该 trait 的唯一预期方式。虽然这句话在技术上是正确的(在当前 Rust 中确实很少显式调用 from_iter()),但这个暗示可能是无意的。而且这与将 FromIterator 移动到预导入模块中的做法相矛盾,这样做的目的显然是为了方便直接调用它。你认为文档中的“很少被显式调用”这一说法应该省略吗? - user4815162342
我个人认为Vec::from_iter()没有必要被反对,我同意这个评论听起来像是想要阻止直接使用该函数。特别是现在我们可以写成HashMap::<_, _>::from_iter([(key, value), ...]),在我看来,这不仅比[(key, value),...].into_iter().collect::<HashMap<_, _>>()更加简洁,而且也更易读。 - Sven Marnach
1
你并不是唯一一个认为这个注释应该被修改的人:https://github.com/rust-lang/rust/issues/90107 - Sven Marnach

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