gpt4 book ai didi

generics - 为什么 `&(?Sized + Trait)`无法转换为 `&dyn Trait`?

转载 作者:行者123 更新时间:2023-12-03 11:48:52 26 4
gpt4 key购买 nike

在下面的代码中,不可能从对实现相同特征的动态大小类型的引用中获得对特征对象的引用。为什么会这样呢?如果我可以同时调用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创建特征对象;
  • 如果Tdyn Foo,则vtable指针是引用的一部分,可以直接复制到输出中。

  • 但是还有第三种可能性在起作用:
  • 如果T是不是dyn Foo的某种非大小型类型,即使特征是对象安全的,impl Foo for T也没有vtable。

  • 没有vtable的原因是,针对具体类型的vtable假定 self指针是精简指针。当您在 dyn Trait对象上调用方法时,将使用vtable指针查找函数指针,并且仅将数据指针传递给该函数。

    但是,假设您为一个无大小的类型实现了一个(n object-safe)特征:
    trait Bar {}
    trait Foo {
    fn foo(&self);
    }

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

    如果此 impl有一个vtable,则它必须接受胖指针,因为 impl可能会使用 Bar的方法,这些方法是在 self上动态调度的。

    这导致两个问题:
  • 无法将Bar vtable指针存储在&dyn Foo对象中,该对象的大小只有两个指针(数据指针和Foo vtable指针)。
  • 即使您同时拥有两个指针,也无法将“胖指针” vtable与“瘦指针” vtable混合使用和匹配,因为必须以不同的方式调用它们。

  • 因此,即使 dyn Bar实现了 Foo,也无法将 &dyn Bar转换为 &dyn Foo

    尽管未使用vtables实现切片(另一种未调整大小的类型),但指向它们的指针仍然很胖,因此对 impl Foo for [i32]也有相同的限制。

    在某些情况下,您可以使用 CoerceUnsized (仅在Rust 1.36以后的夜晚)来表示“必须对 &dyn FooTrait强制”的边界。不幸的是,我看不到如何在您的情况下应用此功能。

    也可以看看
  • What is a "fat pointer" in Rust?
  • Use trait object to pass str in rust有一个具体示例,该实例是对不能强制对特征对象的引用的未定型类型(str)的引用。
  • 关于generics - 为什么 `&(?Sized + Trait)`无法转换为 `&dyn Trait`?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61653006/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com