如何在 Rust 中按值传递装箱特征对象?

How to pass a boxed trait object by value in Rust?

我正在编写一些代码,并且有一个特征,该方法具有按值获取 self 的方法。我想在 Box 的特征对象上调用此方法(消耗 Box 及其值)。这可能吗?如果是,怎么做?

就代码而言,最小示例类似于以下(不完整)代码:

trait Consumable {
    fn consume(self) -> u64;
}
fn consume_box(ptr: Box<dyn Consumable>) -> u64 {
    //what can I put here?
}

我的问题是如何用指定的签名填充函数 consume_box,以便返回的值是通过在 Box 上调用 consume 获得的任何值值。

我最初写过

ptr.consume()

作为函数的主体,虽然我意识到这不是完全正确的想法,因为它没有理解我希望 Box 被消耗的事实,而不仅仅是它的内容,但这是我唯一能想到的。这不编译,报错:

cannot move a value of type dyn Consumable: the size of dyn Consumable cannot be statically determined

这让我有些惊讶,作为 Rust 的新手,我曾认为 self 参数的传递方式类似于 C++ 中的右值引用(这正是我想要的 - 在 C++ 中,我可能会通过带有签名 virtual std::uint64_t consume() && 的方法来实现这一点,让 std::unique_ptr 通过虚拟析构函数清理移出的对象),但我猜 Rust 确实是按值传递,将参数移入放在前面 - 所以它拒绝代码是合理的。

问题是,我不确定如何获得我想要的行为,在那里我可以使用 Box'd 特征对象。我尝试使用默认实现向特征添加一个方法,认为这可能会让我在 vtable 中得到一些有用的东西:

trait Consumable {
    fn consume(self) -> u64;
    fn consume_box(me: Box<Self>) -> u64 {
        me.consume()
    }
}

但是,这会产生错误

the trait Consumable cannot be made into an object

当我提到 Box<dyn Consumable> 类型时 - 这并不奇怪,因为编译器弄清楚如何处理参数类型随 Self 变化的函数将是奇迹。

是否可以使用提供的签名实现功能 consume_box - 甚至在必要时修改特征?


如果它有用,更具体地说,这是某种数学表达式表示的一部分 - 也许玩具模型就是具体的实现,大致如下所示:

impl Consumable for u64 {
    fn consume(self) -> u64 {
        self
    }
}
struct Sum<A, B>(A, B);
impl<A: Consumable, B: Consumable> Consumable for Sum<A, B> {
    fn consume(self) -> u64 {
        self.0.consume() + self.1.consume()
    }
}
struct Product<A, B>(A, B);
impl<A: Consumable, B: Consumable> Consumable for Product<A, B> {
    fn consume(self) -> u64 {
        self.0.consume() * self.1.consume()
    }
}
fn parse(&str) -> Option<Box<dyn Consumable> > {
    //do fancy stuff
}

其中,在大多数情况下,事情是普通的旧数据(但由于泛型,它可能是任意大块),但也要让它与传递更多不透明的句柄兼容事情 - 因此希望能够与 Box<dyn Consumable> 一起工作。至少在语言层面,这是一个很好的模型来说明我在做什么——这些对象拥有的唯一资源是内存片段(与多线程无关,也没有自引用的恶作剧)——尽管这模型没有捕捉到我所拥有的用例是一个对实现使用对象而不是仅仅读取它有用的用例,它也没有适当地模拟我想要一个“开放”class 可能的段而不是而不是一组有限的可能性(使得很难做像直接代表树的 enum 的事情) - 因此我为什么要问按值传递而不是试图重写它以按引用传递。

目前不支持。 dyn Consumable 代表一个 unsized 类型,它非常有限,除非通过间接(通过引用或类似 Box 的结构)。

但是 RFC 1909: Unsized RValues 希望放宽其中一些限制。一个能够传递未调整大小的函数参数,例如 self 在这种情况下。此 RFC 的当前实现在使用 unsized_fn_params:

每晚编译时接受您的初始代码
#![feature(unsized_fn_params)]

trait Consumable {
    fn consume(self) -> u64;
}

struct Foo;
impl Consumable for Foo {
    fn consume(self) -> u64 {
        42
    } 
}

fn main () {
    let ptr: Box<dyn Consumable> = Box::new(Foo);
    println!("result is {}", ptr.consume());
}

参见playground

我相信

trait Consumable {
    fn consume(self) -> u64;
}

fn consume_box(val: impl Consumable) -> u64 {
    val.consume()
}

可能会做你想做的事。我只是一个 Rust 专家——或 C++ 专家——但我认为它应该像你在内存行为方面提到的 C++ 中的移动语义一样工作。据我了解,它是一种泛型形式,其中 Rust 为您调用它的每种类型实现函数。