通过通道发送 Vec<Box<Trait>>

4

我正在尝试通过通道发送 Vec<Box<Trait>>。发送部分好像可以工作。在接收到 Vec 后,我尝试迭代它并将内部值的引用传递给函数,但是出现了错误:

error[E0277]: the trait bound `&std::boxed::Box<AwesomeTrait + std::marker::Send>: AwesomeTrait` is not satisfied
  --> src/main.rs:12:13
   |
12 |             K::abc(&something);
   |             ^^^^^^ the trait `AwesomeTrait` is not implemented for `&std::boxed::Box<AwesomeTrait + std::marker::Send>`
   |
note: required by `K::abc`
  --> src/main.rs:57:5
   |
57 |     pub fn abc<T: AwesomeTrait>(something: &T) {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

有没有什么方法可以从 Box 中获取内部值呢?

这里是一个最小化复现的示例。:

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel::<Request>();
    let s = Something::new();
    tx.send(Request::Do(s)).unwrap();

    let z = thread::spawn(move || match rx.recv().unwrap() {
        Request::Do(somethings) => for something in somethings.list.iter() {
            K::abc(&something);
        },
    });

    z.join();
}

pub enum Request {
    Do(Something),
}

pub struct Something {
    list: Vec<Box<AwesomeTrait + Send>>,
}

impl Something {
    pub fn new() -> Self {
        Self { list: Vec::new() }
    }

    pub fn from<T: AwesomeTrait + Send + 'static>(something: T) -> Self {
        let mut list = Vec::with_capacity(1);
        list.push(Box::new(something));
        // Self { list }
        Self { list: Vec::new() }
    }

    pub fn push<T: AwesomeTrait + Send + 'static>(&mut self, something: T) {
        self.list.push(Box::new(something));
    }
}

pub trait AwesomeTrait {
    fn func(&self);
}

pub struct X {}

impl AwesomeTrait for X {
    fn func(&self) {}
}

pub struct K {}

impl K {
    pub fn abc<T: AwesomeTrait>(something: &T) {
        &something.func();
    }
}

我相信你的第一个问题已经在何时不应该为实现该特质的引用实现特质?的问题和答案中得到了回答。如果你有不同意见,请编辑你的问题以解释差异。否则,我们可以将这个问题标记为已经回答。 - Shepmaster
谢谢,你的第一条评论解决了我的第二个问题!我会编辑掉那部分问题,但即使有你提供的建议链接,我仍然不确定如何解决第一个问题。 - Andrew
2个回答

3
让我来评论一下这个表达式的类型:
for s in somethings.list.iter() {
    K::abc(&s);
}

我已经更改了迭代变量的名称,以避免混淆。

  • something 的类型为:Something
  • something.list 的类型为:Vec<Box<AwesomeTrait + Send>>
  • somethings.list.iter() 的类型为 std::slice::Iter<...>(不重要)。
  • s 的类型是&Box<AwesomeTrait + Send>。需要注意的是它是一个对 box 的引用,因为你使用了 iter() 而不是 into_iter()

要获取实际的 AwesomeTrait,您需要解引用 s 以获取 Box,然后再次解引用以到达内部对象:**s

但是,**s 的类型为 AwesomeTrait,您需要该类型的引用,因此必须使用 &**s 获取地址,其类型为 &AwesomeTrait

最终代码将如下所示:

for s in somethings.list.iter() {
    K::abc(&**s);
}

或者如果你愿意消费这个列表:

for s in somethings.list.into_iter() {
    K::abc(&*s);
}

如果你不想考虑要使用多少个*,可以使用AsRef trait,由Box实现,并信任编译器的自动引用:

for s in somethings.list.iter() {
    K::abc(s.as_ref());
}

注意: .into_iter()也可以省略。如果您迭代Vec的引用,则.iter()也可以省略。
for s in somethings.list { //consume the list
    K::abc(&*s);
}

或者:

for s in &somethings.list { //do not consume the list
    K::abc(&**s);
}

你以为你完成了,但还没有…… 这段代码会抛出这个编译器错误:
error[E0277]: the trait bound `AwesomeTrait + std::marker::Send: std::marker::Sized` is not satisfied
  --> src/main.rs:12:13
   |
12 |             K::abc(&**s);
   |             ^^^^^^ `AwesomeTrait + std::marker::Send` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `AwesomeTrait + std::marker::Send`
note: required by `K::abc`
  --> src/main.rs:57:5
   |
57 |     pub fn abc<T: AwesomeTrait>(something: &T) {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

为什么会这样呢?因为你的K::abc需要一个实现了AwesomeTrait的类型的引用,而&AwesomeTrait当然符合要求。但是Trait是一种不定大小的类型(DST),而所有的泛型函数类型参数默认都需要一个Sized类型。
解决方案是在K::abc中添加一个?Sized的无需求限制:
impl K {
    pub fn abc<T: AwesomeTrait + ?Sized>(something: &T) {
        something.func();
    }
}

(您在此函数中有一个不起作用的&,我已将其删除)。

那个?Sized的限制是您不能声明类型为T的变量或参数,只能声明&T&mut TBox<T>等类型...但您当前的代码没有违规,所以没有问题。


1
哈哈,我漏掉了对?Sized边界的限制,谢谢你提醒! - Ki Chjang
哇,感谢您的精彩解释,它很有效并且让人感到非常合理!出于好奇,这是使用 Rust 较长时间后自然而然就掌握的东西吗?似乎有点难记住所有这些内容。感觉我必须为每个想要使用的第二个函数检查文档。 - Andrew
1
@Andrew:是的,我曾经在Rust方面纠结了很长时间。但有一天,它突然间在我的脑海中理解了,并且所有的事情都变得清晰起来。现在当我使用其他语言编程时,我觉得它们笨重而不可靠 ;-)。不过话说回来,每隔两个函数我还是要查看stdlib文档,不过现在我可以完全理解我所读到的内容。 - rodrigo

0

你很可能想要取消引用 Box<Trait> 以便取回一个 Trait,但这显然是一个不定大小的类型,所以你需要立即将其转换为引用,如下所示:

K::abc(&*something)

但是等等!iter()并不会消耗 Vec<Box<Trait>> 的所有权,因此每个元素都是类型为 &Box<Trait>。为了解决这个问题,我们需要调用 into_iter()

for something in somethings.list.into_iter() {
    K::abc(&*something);
}

谢谢,我刚试了一下,但似乎还是不行。AwesomeTrait + std::marker::Send 在编译时大小未知。 - Andrew

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