![](/img/trans.png)
[英]Trait with function without "self" as parameter cannot be made into an object
[英]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 self
、 self: &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永远不会存在。 因为它是不健全的(即使忽略连贯性问题)。
接收器不可分派但仍然是有效接收器类型(只是不是对象安全)的唯一情况是存在多于一级的指针间接。 例如,
self: &&Self
,self: &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 self
或self: Box<Self>
。 选择哪一个取决于您是否需要所有权(使用Box
)或不需要(使用参考)。 无论如何, &mut Box<Self>
并不是那么有用(它&mut T
相比的唯一优势是您可以替换盒子而不仅仅是它的内容,但是当您这样做时,这通常是一个错误)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.