如何在 Rust 中按值传递装箱的 trait 对象? [英] How to pass a boxed trait object by value in Rust?

查看:31
本文介绍了如何在 Rust 中按值传递装箱的 trait 对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一些代码,并且有一个特性,它带有一个按值获取 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());
}

参见 playground.

这篇关于如何在 Rust 中按值传递装箱的 trait 对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆