繁体   English   中英

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

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

我有这个代码片段( 操场):

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
    }
}

它不编译。 错误是:

特征Scruffy不能变成 object

,以及提示:

因为无法调度方法scruff_up's self参数。

我检查了“ E0038 ”错误描述,但我无法弄清楚我的错误属于哪一类。

我还阅读了“Rust 参考”中的“ 对象安全”条目,我相信这与“所有关联函数必须要么可以从特征对象调度”相匹配,但我不确定,部分原因是我不是确定“接收者”在这种情况下的含义。

您能否为我澄清一下这段代码有什么问题以及为什么它不起作用?

问题是当您将其作为参考传递时,因为内部类型可能尺寸不合适(例如,特征 object,就像您传入Box<Fluffy>一样)编译器没有足够的信息来弄清楚如何调用它的方法。 如果你将它限制为大小的对象(比如你的TeddyBear )它应该编译

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

接收者是self&self&mut selfself: &mut Box<Self>等等)。

请注意,您从参考文献中引用的列表同时列出了Box<Self>&mut Self ,但没有列出&mut Box<Self> ,也没有说允许这些类型的组合。

这确实是被禁止的。 至于为什么,就有点复杂了。

为了使一个类型成为有效的接收者,它需要满足以下条件:

给定实现Trait的任何类型Self和接收器类型Receiver ,接收器类型应为自身实现DispatchFromDyn<dyn Trait> ,并将所有Self出现的Self替换为dyn Trait

例如:

&self (具有&Self类型)必须实现DispatchFromDyn<&dyn Trait>它确实如此

Box<Self>必须实现DispatchFromDyn<Box<dyn Trait>>它确实做到了

但是为了让&mut Box<Self>成为对象安全的接收器,它需要实现DispatchFromDyn<&mut Box<dyn Trait>> 您想要的是DispatchFromDyn<&mut T> for &mut U where U: DispatchFromDyn<T>

这个 impl永远不会存在。 因为它是不健全的(即使忽略连贯性问题)。

正如计算这个的 rustc 代码中所解释的

接收器不可分派但仍然是有效接收器类型(只是不是对象安全)的唯一情况是存在多于一级的指针间接。 例如, self: &&Selfself: &Rc<Self>self: Box<Box<Self>> 在这些情况下,没有办法,或者至少没有廉价的方法,将接收器从Self = dyn Trait的版本强制转换为Self = T的版本,其中T是特征 object 包含的未知擦除类型,因为需要强制的 object 在指针后面。

这个问题是 Rust 如何处理dyn Trait所固有的。

dyn Trait是一个胖指针:它实际上是两个字的大小。 一个是指向数据的指针,另一个是指向 vtable 的指针。

当您在dyn Trait上调用方法时,编译器会在 vtable 中查找,找到具体类型的方法(在编译时未知,但在运行时已知),然后调用它。

如果没有例子,这一切可能非常抽象:

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);
}

编译器生成如下代码:

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);
}

现在假设指向该值的指针位于间接后面。 我将使用Box<&self>因为它很简单,但最好地展示了这个概念,但这个概念也适用于&mut Box<Self> :它们具有相同的布局。 我们将如何为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.
    }
}

你可能会想“那么编译器应该只插入一个对Box::new()的调用!但是除了Box不是这里唯一的一个(例如Rc呢?),我们需要一些特征来抽象这种行为, Rust 从不隐式执行任何艰苦的工作。这是一个设计选择,也是一个重要的选择(与例如 C++ 相对,其中像auto v1 = v;可以通过复制构造函数分配和复制 10GB)。 type 到dyn Trait并返回是隐式完成的:第一个是强制执行的,第二个是当你调用 trait 的方法时。因此,Rust 做的唯一事情是在第一种情况下附加一个 VTable 指针,或者在第二种情况下丢弃它。即使只允许引用( &&&Self ,不需要调用方法,只需获取临时地址)也超过了它。它可能会在意想不到的地方产生严重影响,例如寄存器分配。

那么该怎么办? 您可以使用&mut selfself: Box<Self> 选择哪一个取决于您是否需要所有权(使用Box )或不需要(使用参考)。 无论如何, &mut Box<Self>并不是那么有用(它&mut T相比的唯一优势是您可以替换盒子而不仅仅是它的内容,但是当您这样做时,这通常是一个错误)。

暂无
暂无

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

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