简体   繁体   English

理解 `&amp;mut Box 的“特征 X 不能成为对象”<self> ` 参数</self>

[英]Understanding "the trait X cannot be made into an object" for `&mut Box<Self>` parameter

I've got this code snippet ( playground ):我有这个代码片段( 操场):

struct TeddyBear {
    fluffiness: u8,
}

trait Scruffy {
    fn scruff_up(self: &mut Box<Self>) -> Box<dyn Scruffy>;
}

impl Scruffy for TeddyBear {
    fn scruff_up(self: &mut Box<Self>) -> Box<dyn Scruffy> {
        // do something about the TeddyBear's fluffiness
    }
}

It doesn't compile.它不编译。 The error is:错误是:

the trait Scruffy cannot be made into an object特征Scruffy不能变成 object

, along with the hint: ,以及提示:

because method scruff_up's self parameter cannot be dispatched on.因为无法调度方法scruff_up's self参数。

I checked the " E0038 " error description, but I haven't been able to figure out which category my error falls into.我检查了“ E0038 ”错误描述,但我无法弄清楚我的错误属于哪一类。

I also read the " object-safety " entry in "The Rust Reference", and I believe this matches the "All associated functions must either be dispatchable from a trait object", but I'm not sure, partly because I'm not sure what "receiver" means in that context.我还阅读了“Rust 参考”中的“ 对象安全”条目,我相信这与“所有关联函数必须要么可以从特征对象调度”相匹配,但我不确定,部分原因是我不是确定“接收者”在这种情况下的含义。

Can you please clarify for me what's the problem with this code and why it doesn't work?您能否为我澄清一下这段代码有什么问题以及为什么它不起作用?

The problem is when you pass it in as a reference, because the inner type may not be well-sized (eg a trait object, like if you passed in a Box<Fluffy> ) the compiler doesn't have enough information to figure out how to call methods on it.问题是当您将其作为参考传递时,因为内部类型可能尺寸不合适(例如,特征 object,就像您传入Box<Fluffy>一样)编译器没有足够的信息来弄清楚如何调用它的方法。 If you restrict it to sized objects (like your TeddyBear ) it should compile如果你将它限制为大小的对象(比如你的TeddyBear )它应该编译

trait Scruffy {
    fn scruff_up(self: &mut Box<Self>) -> Box<dyn Scruffy> where Self: Sized;
}

A receiver is the self ( &self , &mut self , self: &mut Box<Self> and so on).接收者是self&self&mut selfself: &mut Box<Self>等等)。

Note that the list you cited from the reference lists both Box<Self> and &mut Self , but does not list &mut Box<Self> nor it says that combinations of these types are allowed.请注意,您从参考文献中引用的列表同时列出了Box<Self>&mut Self ,但没有列出&mut Box<Self> ,也没有说允许这些类型的组合。

This is, indeed, forbidden.这确实是被禁止的。 As for the why, it is a little more complex.至于为什么,就有点复杂了。

In order for a type to be a valid receiver, it needs to hold the following condition:为了使一个类型成为有效的接收者,它需要满足以下条件:

Given any type Self that implements Trait and the receiver type Receiver , the receiver type should implement DispatchFromDyn<dyn Trait> for itself with all Self occurrences of Self replaced with dyn Trait .给定实现Trait的任何类型Self和接收器类型Receiver ,接收器类型应为自身实现DispatchFromDyn<dyn Trait> ,并将所有Self出现的Self替换为dyn Trait

For instance:例如:

&self (has the type &Self ) has to implement DispatchFromDyn<&dyn Trait> , which it does . &self (具有&Self类型)必须实现DispatchFromDyn<&dyn Trait>它确实如此

Box<Self> has to implement DispatchFromDyn<Box<dyn Trait>> , which it does . Box<Self>必须实现DispatchFromDyn<Box<dyn Trait>>它确实做到了

But in order for &mut Box<Self> to be an object-safe receiver, it would need to impl DispatchFromDyn<&mut Box<dyn Trait>> .但是为了让&mut Box<Self>成为对象安全的接收器,它需要实现DispatchFromDyn<&mut Box<dyn Trait>> What you want is kind of blanket implementation DispatchFromDyn<&mut T> for &mut U where U: DispatchFromDyn<T> .您想要的是DispatchFromDyn<&mut T> for &mut U where U: DispatchFromDyn<T>

This impl will never exist.这个 impl永远不会存在。 Because it is unsound (even ignoring coherence problems).因为它是不健全的(即使忽略连贯性问题)。

As explained in the code in rustc that calculates this : 正如计算这个的 rustc 代码中所解释的

The only case where the receiver is not dispatchable, but is still a valid receiver type (just not object-safe), is when there is more than one level of pointer indirection.接收器不可分派但仍然是有效接收器类型(只是不是对象安全)的唯一情况是存在多于一级的指针间接。 Eg, self: &&Self , self: &Rc<Self> , self: Box<Box<Self>> .例如, self: &&Selfself: &Rc<Self>self: Box<Box<Self>> In these cases, there is no way, or at least no inexpensive way, to coerce the receiver from the version where Self = dyn Trait to the version where Self = T , where T is the unknown erased type contained by the trait object, because the object that needs to be coerced is behind a pointer.在这些情况下,没有办法,或者至少没有廉价的方法,将接收器从Self = dyn Trait的版本强制转换为Self = T的版本,其中T是特征 object 包含的未知擦除类型,因为需要强制的 object 在指针后面。

The problem is inherent to how Rust handles dyn Trait .这个问题是 Rust 如何处理dyn Trait所固有的。

dyn Trait is a fat pointer: it is actually two words sized. dyn Trait是一个胖指针:它实际上是两个字的大小。 One is a pointer to the data, and the other is a pointer to the vtable.一个是指向数据的指针,另一个是指向 vtable 的指针。

When you call a method on dyn Trait , the compiler look ups in the vtable, find the method for the concrete type (which is unknown at compilation time, but known at runtime), and calls it.当您在dyn Trait上调用方法时,编译器会在 vtable 中查找,找到具体类型的方法(在编译时未知,但在运行时已知),然后调用它。

This all may be very abstract without an example:如果没有例子,这一切可能非常抽象:

trait Trait {
    fn foo(&self);
}

impl Trait for () {
    fn foo(&self) {}
}

fn call_foo(v: &dyn Trait) {
    v.foo();
}

fn create_dyn_trait(v: &impl Trait) {
    let v: &dyn Trait = v;
    call_foo(v);
}

The compiler generates code like:编译器生成如下代码:

trait Trait {
    fn foo(&self);
}

impl Trait for () {
    fn foo(&self) {}
}

struct TraitVTable {
    foo: fn(*const ()),
}
static TRAIT_FOR_UNIT_VTABLE: TraitVTable = TraitVTable {
    foo: unsafe { std::mem::transmute(<() as Trait>::foo) },
};

type DynTraitRef = (*const (), &'static TraitVTable);
impl Trait for dyn Trait {
    fn foo(self: DynTraitRef) {
        (self.1.foo)(self.0)
    }
}

fn call_foo(v: DynTraitRef) {
    v.foo();
}

fn create_dyn_trait(v: &impl Trait) {
    let v: DynTraitRef = (v as *const (), &TRAIT_FOR_UNIT_VTABLE);
    call_foo(v);
}

Now suppose that the pointer to the value is behind an indirection.现在假设指向该值的指针位于间接后面。 I'll use Box<&self> because it's simple but demonstrates the concept best, but the concept applies to &mut Box<Self> too: they have the same layout.我将使用Box<&self>因为它很简单,但最好地展示了这个概念,但这个概念也适用于&mut Box<Self> :它们具有相同的布局。 How will we write foo() for impl Trait for dyn Trait ?我们将如何为impl Trait for dyn Trait编写foo()

trait Trait {
    fn foo(self: Box<&Self>);
}

impl Trait for () {
    fn foo(self: Box<&Self>) {}
}

struct TraitVTable {
    foo: fn(Box<*const ()>),
}
static TRAIT_FOR_UNIT_VTABLE: TraitVTable = TraitVTable {
    foo: unsafe { std::mem::transmute(<() as Trait>::foo) },
};

type DynTraitRef = (*const (), &'static TraitVTable);
impl Trait for dyn Trait {
    fn foo(self: Box<&DynTraitRef>) {
        let concrete_foo: foo(&*const ()) = self.1.foo;
        let data: *const () = self.0;
        concrete_foo(data) // We need to wrap `data` in `Box! Error.
    }
}

You may think "then the compiler should just insert a call to Box::new() ! But besides Box not being the only one here (what with Rc , for example?) and we will need some trait to abstract over this behavior, Rust never performs any hard work implicitly . This is a design choice, and an important one (as opposed to eg C++, where an innocent-looking statement like auto v1 = v; can allocate and copy 10GB by a copy constructor). Converting a type to dyn Trait and back is done implicitly: the first one by a coercion, the second one when you call a method of the trait. Thus, the only thing that Rust does for that is attaching a VTable pointer in the first case, or discarding it in the second case. Even allowing only references ( &&&Self , no need to call a method, just take the address of a temporary) exceeds that. And it can have severe implications in unexpected places, eg register allocation.你可能会想“那么编译器应该只插入一个对Box::new()的调用!但是除了Box不是这里唯一的一个(例如Rc呢?),我们需要一些特征来抽象这种行为, Rust 从不隐式执行任何艰苦的工作。这是一个设计选择,也是一个重要的选择(与例如 C++ 相对,其中像auto v1 = v;可以通过复制构造函数分配和复制 10GB)。 type 到dyn Trait并返回是隐式完成的:第一个是强制执行的,第二个是当你调用 trait 的方法时。因此,Rust 做的唯一事情是在第一种情况下附加一个 VTable 指针,或者在第二种情况下丢弃它。即使只允许引用( &&&Self ,不需要调用方法,只需获取临时地址)也超过了它。它可能会在意想不到的地方产生严重影响,例如寄存器分配。

So, what to do?那么该怎么办? You can take &mut self or self: Box<Self> .您可以使用&mut selfself: Box<Self> Which one to choose depends on whether you need ownership (use Box ) or not (use a reference).选择哪一个取决于您是否需要所有权(使用Box )或不需要(使用参考)。 And anyway, &mut Box<Self> is not so useful (its only advantage over &mut T is that you can replace the box and not just its contents , but when you do that that's usually a mistake).无论如何, &mut Box<Self>并不是那么有用(它&mut T相比的唯一优势是您可以替换盒子而不仅仅是它的内容,但是当您这样做时,这通常是一个错误)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM