簡體   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