如何在 Rust 中按值传递装箱的 trait 对象? [英] How to pass a boxed trait object by value in Rust?
问题描述
我正在编写一些代码,并且有一个特性,它带有一个按值获取 self
的方法.我想在 Box
的 trait 对象上调用这个方法(消耗 Box
及其值).这可能吗?如果是这样,如何?
I was writing some code and had a trait with a method that takes self
by value. I want to call this method on a Box
'd trait object (consuming the Box
and its value). Is this possible? If so, how?
在代码方面,一个最小的例子看起来像下面的(不完整的)代码:
In terms of code, a minimal example looks like the following (incomplete) code:
trait Consumable {
fn consume(self) -> u64;
}
fn consume_box(ptr: Box<dyn Consumable>) -> u64 {
//what can I put here?
}
我的问题是如何用指定的签名填充函数 consume_box
以便返回的值是通过在 上调用
的值.consume
获得的任何值装箱
My question is how to fill in the function consume_box
with the specified signature so that the value returned is whatever value would be gotten by calling consume
on the Box
'd value.
我最初写的
ptr.consume()
作为函数的主体,虽然我意识到这不是一个正确的想法,因为它没有理解我希望使用 Box
的事实,而不仅仅是它的内容,但这是我唯一能想到的.这不会编译,给出一个错误:
as the body of the function, though I realize this isn't quite the right idea, since it doesn't get across the fact that I want the Box
to be consumed, not just its contents, but it's the only thing I could think of. This does not compile, giving an error:
无法移动 dyn Consumable 类型的值:无法静态确定 dyn Consumable 的大小
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 consumer() &&
,让 std::unique_ptr
清理移动的对象通过虚拟析构函数),但我猜 Rust 确实是按值传递,将参数移动到位之前 - 所以它拒绝代码是合理的.
This was somewhat surprising to me, being new to Rust, I had thought that maybe the self
argument was passed similarly to an rvalue reference in C++ (which is really what I want - in C++, I would probably implement this by a method with the signature virtual std::uint64_t consume() &&
, letting a std::unique_ptr
clean up the moved-from object via a virtual destructor), but I guess Rust is truly passing by value, moving the argument into place prior - so it's reasonable that it rejects the code.
问题是,我不确定如何获得我想要的行为,在那里我可以使用 Box
的 trait 对象.我尝试使用默认实现向 trait 添加一个方法,认为这可能会让我在 vtable 中得到一些有用的东西:
Trouble is, I'm not sure how to get the behavior I want, where I can consume a Box
'd trait object. I tried adding a method to the trait with a default implementation, thinking that might get me something useful in the vtable:
trait Consumable {
fn consume(self) -> u64;
fn consume_box(me: Box<Self>) -> u64 {
me.consume()
}
}
然而,这会产生错误
特性Consumable
不能变成对象
当我提到 Box
类型时 - 这并不奇怪,因为编译器会弄清楚如何处理参数类型随 Self
本来就是奇迹.
when I mention the Box<dyn Consumable>
type - which is not so surprising, since the compiler figuring out what to do with a function whose argument type varied with Self
would have been miraculous.
是否可以使用提供的签名来实现函数 consume_box
- 甚至在必要时修改特征?
Is it possible to implement the function consume_box
with the provided signature - even modifying the trait if necessary?
如果它有用,更具体地说,这是某些数学表达式的一种表示形式的一部分 - 也许一个玩具模型就是大致如下所示的具体实现:
If it's useful, more specifically, this is part of a sort of representation of some mathematical expressions - maybe a toy model would be that specific implementations that look roughly like:
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
.至少在语言层面,这是一个很好的模型,说明我在做什么——这些对象拥有的唯一资源是内存片段(与多线程无关,也没有自我引用的恶作剧)——尽管这模型没有捕捉到我拥有的用例是一个对实现使用对象而不是仅仅读取它很有用的用例,也没有适当地建模我想要一个开放"的对象.类可能的段,而不是一组有限的可能性(使得很难做像 enum
这样的直接代表树的事情) - 因此为什么我问的是通过值传递而不是试图重写它通过引用传递.
where, for the most part, things are plain old data (but arbitrarily large blocks of it, potentially, due to the generics), but to also have this be compatible with passing around more opaque handles to these sorts of things - hence the desire to be able to work with Box<dyn Consumable>
. At least at the language level, this is a good model of what sort of things I'm up to - the only resources owned by these objects are pieces of memory (nothing to do with multithreading and no self-referential shenanigans) - although this model doesn't capture that the use case I have is one where it's useful for the implementation to consume the object rather than to merely read it nor does it appropriately model that I want an "open" class of possible segments rather than a finite set of possiblities (making it hard to do something like an enum
that represents a tree directly) - hence why I'm asking about passing by value rather than trying to rewrite it to pass by reference.
推荐答案
目前不支持.dyn Consumable
代表一个 unsized 类型,除了通过间接(通过引用或 Box
之类的结构)之外,它非常有限.
This is not currently supported. A dyn Consumable
represents an unsized type which are very limited except through indirection (via references or Box
-like structs).
但是有 RFC 1909: UnsizedRValues 希望放宽其中一些限制.可以传递未定义大小的函数参数,例如在这种情况下的 self
.使用 unsized_fn_params
在夜间编译时,此 RFC 的当前实现接受您的初始代码:
However there is RFC 1909: Unsized RValues which hopes to loosen some of these limits. One being able to pass unsized function parameters, like self
in this case. The current implementation of this RFC accepts your initial code when compiled on nightly with 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());
}
这篇关于如何在 Rust 中按值传递装箱的 trait 对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!