![](/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.