为什么使用盒装对象而不是特征对象?

Why using boxed objects over trait objects?

在《Rust for Rustaceans》一书中,作者写道:

Broadly speaking, though, you’ll want to use static dispatch in your libraries and dynamic dispatch in your binaries. In a library, you want to allow your users to decide what kind of dispatch is best for them, since you don’t know what their needs are.

我猜,在二进制情况下,他指的是:

fn flexible_dispatch_method(_: &dyn MyTrait) {}

// static dispatch
//
let obj = MyType {};
flexible_dispatch_method(&obj);

// dynamic dispatch
//
let trait_obj: &dyn MyTrait = &MyType {};
flexible_dispatch_method(trait_obj);

综上所述,使用盒装对象而不是特征对象有什么好处?是不是因为需要使用lifetimes:

fn return_new_trait_object<'a>() -> &'a dyn MyTrait {
    &MyType {}
}

还是有其他问题?根据我的理解,在动态调度的情况下,对象需要在堆中分配,所以我认为与装箱对象没有太大区别。

我认为您可能误解了一些事情。

  • (通常)每当您在非 dyn Trait 的具体类型上调用方法时,就会发生静态分派。这是编译器决定在 compile-time.
  • 处调用哪个函数的时候
  • 特征对象是任何指向 dyn Trait 的指针,包括 Box<dyn Trait>&dyn TraitArc<dyn Trait> 等。当您通过 dyn Trait, 编译器插入代码来查找要在运行时调用的函数,允许多态性和更大的灵活性。

在您的代码中,flexible_dispatch_method(_: &dyn MyTrait) 始终使用动态分派,正如它的参数具有类型 &dyn MyTrait 所表明的那样。静态分派的示例如下:

fn flexible_dispatch_method<T: MyTrait + ?Sized>(_: &T) {}

使用此声明,您的第一次使用将使用静态分派,第二次使用动态分派。

动态调度更灵活一些,因为它避免了到处都是泛型。这对于编写您可能希望具有多态性并轻松添加新实现的大型应用程序很有用。然而,动态调度会产生性能成本,因此库应尽可能将调度选择权留给调用者。

根据何时使用 &dyn TraitBox<dyn Trait>:这完全基于所有权。如果你想要一个拥有的特征对象使用 Box<dyn Trait>,如果你想要一个借来的特征对象使用 &dyn Trait.