我正在编写一些代码,并且有一个具有通过值传递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
对于我这个新手来说,这有些令人惊讶。我曾认为self
参数可能类似于C++中的一个右值引用(这正是我想要的——在C++中,我可能会通过签名为virtual std::uint64_t consume() &&
的方法实现它,让std::unique_ptr
通过虚析构函数清理移动过来的对象),但我猜Rust确实是传值的,将参数移动到位,所以拒绝了这段代码。
问题在于,我不知道如何获得我想要的行为,即如何消耗一个装箱的特质对象。我尝试给特质添加一个具有默认实现的方法,认为这可能会在虚函数表中给我带来一些有用的东西:
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
那样做)——这就是为什么我要询问是否通过值传递而不是尝试将其重写为通过引用传递。
Self
的大小在编译时已知,则可以将: Sized
添加到特质中。 - vallentintrait Consumable: Sized
,但它报错说 "the traitConsumable
cannot be into an object ... because it requiresSelf: Sized
" - 我认为它在抱怨编译时不知道特质对象的大小,而不是任何特定实现者的大小(尽管我也没有试图在奇怪的东西上实现特质)。 - Milo Brandt