簡體   English   中英

為什么不能將 `&(?Sized + Trait)` 轉換為 `&dyn Trait`?

[英]Why can't `&(?Sized + Trait)` be cast to `&dyn Trait`?

在下面的代碼中,不可能從對實現相同特征的動態大小類型的引用中獲取對特征對象的引用。 為什么會這樣? 如果我可以使用兩者來調用 trait 方法,那么&dyn Trait&(?Sized + Trait)之間到底有什么區別?

實現FooTraitContainerTrait的類型可能例如具有type Contained = dyn FooTraittype Contained = T ,其中T是實現FooTrait的具體類型。 在這兩種情況下,獲得&dyn FooTrait 我想不出另一種不起作用的情況。 為什么在FooTraitContainerTrait的通用情況下這不可能?

trait FooTrait {
    fn foo(&self) -> f64;
}

///

trait FooTraitContainerTrait {
    type Contained: ?Sized + FooTrait;
    fn get_ref(&self) -> &Self::Contained;
}

///

fn foo_dyn(dyn_some_foo: &dyn FooTrait) -> f64 {
    dyn_some_foo.foo()
}

fn foo_generic<T: ?Sized + FooTrait>(some_foo: &T) -> f64 {
    some_foo.foo()
}

///

fn foo_on_container<C: FooTraitContainerTrait>(containing_a_foo: &C) -> f64 {
    let some_foo = containing_a_foo.get_ref();
    // Following line doesn't work:
    //foo_dyn(some_foo)
    // Following line works:
    //some_foo.foo()
    // As does this:
    foo_generic(some_foo)
}

取消注釋foo_dyn(some_foo)行會導致編譯器錯誤

error[E0277]: the size for values of type `<C as FooTraitContainerTrait>::Contained` cannot be known at compilation time
  --> src/main.rs:27:22
   |
27 |     foo_dyn(contained)
   |             ^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `<C as FooTraitContainerTrait>::Contained`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = help: consider adding a `where <C as FooTraitContainerTrait>::Contained: std::marker::Sized` bound
   = note: required for the cast to the object type `dyn FooTrait`

這個問題可以簡化為以下簡單示例(感謝turbulencetoo ):

trait Foo {}

fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
    arg
}

乍一看,正如您所觀察到的,它確實應該可以編譯:

  • 如果TSized ,編譯器靜態地知道它應該使用什么 vtable 來創建 trait 對象;
  • 如果Tdyn Foo ,則 vtable 指針是引用的一部分,可以直接復制到輸出。

但是還有第三種可能性會影響工作:

  • 如果T是一些不是dyn Foo未定大小的類型,即使 trait 是對象安全的,也沒有 vtable for impl Foo for T

沒有 vtable 的原因是因為具體類型的 vtable 假定self指針是細指針。 當你在一個dyn Trait對象上調用一個方法時,vtable 指針用於查找一個函數指針,並且只有數據指針被傳遞給函數。

但是,假設您為未確定大小的類型實現了 (n object-safe) trait:

trait Bar {}
trait Foo {
    fn foo(&self);
}

impl Foo for dyn Bar {
    fn foo(&self) {/* self is a fat pointer here */}
}

如果這方面的一個虛函數表impl ,那就得接受脂肪指針,因為impl可以使用的方法Bar這是在動態分派self

這會導致兩個問題:

  • 沒有地方可以在&dyn Foo對象中存儲Bar vtable 指針,該對象的大小只有兩個指針(數據指針和Foo vtable 指針)。
  • 即使您有兩個指針,也不能將“胖指針”vtable 與“瘦指針”vtable 混合和匹配,因為它們必須以不同的方式調用。

因此,即使dyn Bar實現了Foo ,也不可能將&dyn Bar變成&dyn Foo

盡管切片(另一種未調整大小的類型)沒有使用 vtables 實現,但指向它們的指針仍然很胖,因此同樣的限制適用impl Foo for [i32]

在某些情況下,您可以使用CoerceUnsized (僅在 Rust 1.36 中每晚使用)來表達諸如“必須&dyn FooTrait&dyn FooTrait ”之類的邊界。 不幸的是,我不知道如何在您的情況下應用它。

也可以看看

不確定這是否能解決您的具體問題,但我確實使用以下技巧解決了我的問題:

我在FooTrait添加了以下方法:

fn as_dyn(&self) -> &dyn FooTrait;

不能提供默認的 impl(因為它要求SelfSized ,但將FooTrait限制為Sized禁止為其創建 trait 對象......)。

然而,對於所有Sized實現,它被簡單地實現為

fn as_dyn(&self) -> &dyn FooTrait { self }

所以基本上它限制了FooTrait所有實現的大小,除了dyn FooTrait

在操場上試試

引用自此博客,它很好地解釋了胖指針。

感謝trentcl將問題簡化為:

trait Foo {}

fn make_dyn<T: Foo + ?Sized>(arg: &T) -> &dyn Foo {
    arg
}

這就帶來了如何在不同的?Sized之間進行轉換?

為了回答這個問題,讓我們先看看 Unsized 類型Trait的實現。

trait Bar {
    fn bar_method(&self) {
        println!("this is bar");
    }
}

trait Foo: Bar {
    fn foo_method(&self) {
        println!("this is foo");
    }
}

impl Bar for u8 {}
impl Foo for u8 {}

fn main() {
    let x: u8 = 35;
    let foo: &dyn Foo = &x;
    // can I do
    // let bar: &dyn Bar = foo;
}

那么,你能不能let bar: &dyn Bar = foo; ?

// below is all pseudo code
pub struct TraitObjectFoo {
    data: *mut (),
    vtable_ptr: &VTableFoo,
}

pub struct VTableFoo {
    layout: Layout,
    // destructor
    drop_in_place: unsafe fn(*mut ()),
    // methods shown in deterministic order
    foo_method: fn(*mut ()),
    bar_method: fn(*mut ()),
}

// fields contains Foo and Bar method addresses for u8 implementation
static VTABLE_FOO_FOR_U8: VTableFoo = VTableFoo { ... };

從偽代碼我們可以知道

// let foo: &dyn Foo = &x;
let foo = TraitObjectFoo {&x, &VTABLE_FOO_FOR_U8};
// let bar: &dyn Bar = foo;
// C++ syntax for contructor
let bar = TraitObjectBar(TraitObjectFoo {&x, &VTABLE_FOO_FOR_U8});

bar類型是TraitObjectBar ,而不是TraitObjectFoo類型。 也就是說,您不能將一種類型的結構分配給另一種不同的類型(在 rust 中,在 C++ 中您可以使用 reinterpret_cast)。

你可以做什么來獲得另一個間接級別

impl Bar for dyn Foo {
...
}

let bar: &dyn Bar = &foo;
// TraitObjectFoo {&foo, &VTABLE_FOO_FOR_DYN_FOO}

同樣的事情也適用於 Slice。

鑄造不同的Unsized的解決方法可以通過這個技巧來完成:

// blanket impl for all sized types, this allows for a very large majority of use-cases
impl<T: Bar> AsBar for T {
    fn as_bar(&self) -> &dyn Bar { self }
}

// a helper-trait to do the conversion
trait AsBar {
    fn as_bar(&self) -> &dyn Bar;
}

// note that Bar requires `AsBar`, this is what allows you to call `as_bar`
// from a trait object of something that requires `Bar` as a super-trait
trait Bar: AsBar {
    fn bar_method(&self) {
        println!("this is bar");
    }
}

// no change here
trait Foo: Bar {
    fn foo_method(&self) {
        println!("this is foo");
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM