4

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

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

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

我的问题是如何consume_box用指定的签名填充函数,以便返回的值是通过调用'd 值获得的任何值consumeBox

我最初写过

ptr.consume()

作为函数的主体,虽然我意识到这不是一个正确的想法,因为它没有传达出我想要Box被消费的事实,不仅仅是它的内容,而是我唯一能想到的东西. 这不编译,给出一个错误:

无法移动 dyn Consumable 类型的值:无法静态确定 dyn Consumable 的大小

这对我来说有点令人惊讶,作为 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()
    }
}

但是,这会产生错误

特质Consumable不能变成对象

当我提到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>. 至少在语言级别上,这是一个很好的模型来说明我在做什么——这些对象拥有的唯一资源是内存块(与多线程无关,也没有自我引用的恶作剧)——尽管这模型没有捕捉到我拥有的用例是一个对实现使用对象而不是仅仅读取它很有用的用例,也没有适当地建模我想要“打开”enum直接代表一棵树) - 因此我问的是按值传递而不是尝试重写它以通过引用传递。

4

2 回答 2

7

目前不支持此功能。Adyn Consumable表示一个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());
}

操场上见。

于 2020-12-12T04:30:48.883 回答
0

我相信

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

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

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

于 2020-12-29T20:55:13.453 回答