【问题标题】:How can I clone a Vec<Box<dyn Trait>>?如何克隆 Vec<Box<dyn Trait>>?
【发布时间】:2021-11-15 06:02:29
【问题描述】:

我想实现一个函数,该函数采用不可变的&amp;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&lt;Box&lt;dyn Foo&gt;&gt; = Vec::new(); 但是会导致各种我不知道如何修复的错误。

如何使这个版本工作,为什么它与第一个版本不同?

尝试 2

更接近我正在寻找的东西是这样的。 let mut v_copy = v.clone(); 但这不起作用。这个版本可以修复吗?

【问题讨论】:

  • 您可以使用let mut v_copy: Vec&lt;_&gt; = v.iter().collect()缩短复制时间。一个更好的问题是,当您只将一种类型放入 Vec? 时,为什么需要动态调度开始?那将是get rid of boxes。你也可以给sort_and_print一个价值获取闭包,并完全摆脱这个特征:play.rust-lang.org/…
  • 我发布了简化的代码。真正的代码使用不同的结构,它们都实现了Foo trait。感谢您的代码,它有效!所以我们实际上是在创建 Vec&lt;&amp;Box&lt;dyn Foo&gt;&gt; 而不是 Vec&lt;Box&lt;dyn Foo&gt;&gt;,但这不是问题。这也回答了问题 1。.iter().collect() 在这些类型的情况下是标准方法吗?
  • 是的,您的代码总是创建一个Vec&lt;&amp;Box&lt;dyn Foo&gt;&gt;,而.iter().collect() 只是一种更简洁的方式。 let x: Vec&lt;_&gt; = &lt;some iterator&gt;.collect() 是一个标准的 Rust 习惯用法,用于为你迭代的东西创建一个向量。

标签: rust


【解决方案1】:

首先,让我们注释类型:

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

&amp;Vec&lt;T&gt; 进行迭代会在&amp;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());
    }
}

但是,我们仍然只有一份参考资料 (&amp;Box&lt;dyn Foo&gt;)。为什么我们不能只克隆向量?

如果我们尝试...

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

咕噜!!

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&lt;dyn Foo&gt;Vec&lt;Box&lt;dyn Foo&gt;&gt; 都没有。

然而,&amp;Vec&lt;Box&lt;dyn Foo&gt;&gt; 实际上确实 impl 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() 的方法采用self 类型的&amp;Vec&lt;Box&lt;dyn Foo&gt;&gt; (v)?”它首先在Clone impl 上为Vec&lt;Box&lt;dyn Foo&gt;&gt; 寻找这种方法(因为Clone::clone() 需要&amp;self,所以对于Vec&lt;Box&lt;dyn Foo&gt;&gt; 它需要&amp;Vec&lt;Box&lt;dyn Foo&gt;&gt;)。不幸的是,这样的 impl 不存在,所以它具有 autoref 的魔力(尝试在 Rust 中调整方法接收器的过程的一部分,您可以阅读更多 here),并为 &amp;&amp;Vec&lt;Box&lt;dyn Foo&gt;&gt; 提出相同的问题。现在我们确实找到了匹配项 - &lt;&amp;Vec&lt;Box&lt;dyn Foo&gt;&gt; as Clone&gt;::clone()!所以这是编译器调用的。

方法的返回类型是什么?好吧,&amp;Vec&lt;Box&lt;dyn Foo&gt;&gt;。这将是v_copy 的类型。现在我们明白了为什么当我们指定另一种类型时,编译器会发疯!当我们没有指定类型时,我们也可以解密错误消息:我们要求编译器在&amp;Vec&lt;Box&lt;dyn Foo&gt;&gt;上调用sort_by_key(),但是这种方法需要&amp;mut Vec&lt;Box&lt;dyn Foo&gt;&gt;(准确地说是&amp;mut [Box&lt;dyn Foo&gt;],但它没关系,因为Vec&lt;T&gt; 可以强制转换为[T])!

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

当我们调用to_vec()、OTOH 时,编译器并没有感到困惑:to_vec() 需要向量的项来实现Clone (where T: Clone),而Box&lt;dyn Foo&gt; 不会发生这种情况。轰隆隆。

现在解决方案应该很清楚了:我们只需要Box&lt;dyn Foo&gt; 来实现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 indeed requires Sized。为什么?

好吧,因为为了克隆一些东西,我们需要返回它自己。我们可以返回dyn Foo 吗?不,因为它可以是任意大小。

所以,让我们手动尝试impl Clone for Box&lt;dyn Foo&gt;(我们可以这样做,即使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 概括了这个想法。

【讨论】:

  • “我们可以做到这一点,即使 Box 没有在我们的 crate 中定义,因为它是一个基本类型”——这并不完全正确。允许这样做的原因是因为Foo 是在我们的 crate 中定义的,因此上游 crate 中不能有任何冲突的实施。
  • @SvenMarnach 当然,但如果Box 不是基本的,那么Box&lt;dyn Foo&gt; 不是本地的,我们无法为其实现外来特征(Clone)。通过这种方式,Box 是特殊的。编辑了答案以澄清这一点。
  • 是的,对不起,我实际上有点混淆了。你完全正确。事实上,标准库理论上可以T: ?Sized添加一个通用的impl,这会与这里的impl冲突,但是由于Box#[fundamental],所以无论如何我们都可以添加这个impl。
  • @SvenMarnach 标准库不允许定义这样的impl 因为它会破坏我们的代码;事实上,the definition of fundamental types 是一种基本类型,如果“在其上实现全面实现是一项重大更改”。
【解决方案2】:

您可以使用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());
    }
}

Playground

顺便说一句,通过引用接受向量通常更好地表示为接受切片as explained here,因此上述答案接受切片。

您可以使用itertools crate 中的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&lt;dyn Foo&gt;,这是不必要的,可能很昂贵,而且很复杂(如另一个答案中所述)。

【讨论】:

  • Vec::from_iter(v) 更短。在 Rust 2021 中,这是前奏,所以我将来会更多地使用它。
  • @SvenMarnach 有趣的是,docs 声明 from_iter() “很少显式调用”,这听起来像是 .collect() 是使用该特征的唯一预期方式。虽然这句话在技术上是正确的(from_iter() is 在当前的 Rust 中很少显式调用),但其含义可能是无意的。也和FromIterator被移到前奏相矛盾,大概是为了方便直接调用吧。您认为文档中应该省略“很少显式调用”这句话吗?
  • 我个人认为没有理由不鼓励Vec::from_iter(),并且我同意该评论听起来像是不鼓励直接使用该功能。特别是现在我们可以写HashMap::&lt;_, _&gt;::from_iter([(key, value), ...]),在我看来,它不仅比[(key, value),...].into_iter().collect::&lt;HashMap&lt;_, _&gt;&gt;() 更简洁,而且更具可读性。
  • 您不是唯一一个认为应该更改评论的人:github.com/rust-lang/rust/issues/90107
猜你喜欢
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-01
  • 2021-12-04
  • 1970-01-01
  • 2020-12-15
相关资源
最近更新 更多