为什么在以“ Self:Sized”为界时无法调用特征对象上的函数?

时间:2018-08-13 12:13:10

标签: rust

我有以下代码:

trait Bar {
    fn baz(&self, arg: impl AsRef<str>) where Self: Sized;
}

struct Foo;

impl Bar for Foo {
    fn baz(&self, arg: impl AsRef<str>) {}
}

fn main() {
    let boxed: Box<dyn Bar> = Box::new(Foo);
    boxed.baz();
}

playground

哪个会导致此错误:

error: the `baz` method cannot be invoked on a trait object
  --> src/main.rs:13:11
   |
13 |     boxed.baz();
   |           ^^^

为什么这不可能?删除Self: Sized边界时,它可以正常工作,但是,我不能使用泛型,这对于调用者来说很舒服。


这不是 Why does a generic method inside a trait require trait object to be sized? 的副本。这个问题问,为什么不能从特征对象调用baz。我问,为什么需要绑定。这已经是discussed

2 个答案:

答案 0 :(得分:2)

因为Rust的泛型系统通过单态化工作。

例如,在Java中,泛型函数中的类型参数转换为Object类型的变量,并根据需要进行强制转换。像这样的语言中的泛型只是作为一种工具来帮助验证代码中类型的正确性。

Rust和C ++等语言对泛型使用单态化。对于类型参数的每个组合,将调用通用函数,并生成专用机器码,该机器码将这些函数与类型参数的那些组合一起运行。该函数是 monomorphized 。这样可以将数据存储在适当的位置,消除了转换的开销,并允许通用代码在该类型参数上调用“静态”函数。

那么为什么不能在特征对象上这样做呢?

许多语言(包括Rust)的特质对象都是使用 vtable 实现的。当您具有某种指向特征对象的指针(原始,引用,Box,引用计数器等)时,它包含两个指针:指向数据的指针和指向 vtable条目的指针。 vtable条目是功能指针的集合,存储在一个不变的内存区域中,该指针指向该特征方法的实现。因此,当您在特征对象上调用方法时,它会在vtable中查找实现的函数指针,然后间接跳转到该指针。

不幸的是,如果在编译时Rust编译器不知道实现该功能的代码,则Rust编译器无法使函数单态化(在特征对象上调用方法时就是这种情况)。因此,您不能在特征对象上调用泛型函数(当然,泛型超过类型)。

-编辑-

听起来您在问为什么需要: Sized限制。

: Sized使其无法将特征用作特征对象。我想可能有两种选择。 Rust可以隐式地使具有泛型函数的任何特征都不安全。 Rust还可以隐式阻止在特征对象上调用泛型函数。

但是,Rust试图明确地说明编译器在做什么,这些隐式方法会违背这种做法。无论如何,对于初学者来说,尝试在trait对象上调用泛型函数并使其无法编译会造成混淆吗?

相反,Rust让您明确使整个特征不安全

trait Foo: Sized {

或者明确地使某些功能仅在静态分派中可用

fn foo<T>() where Self: Sized {

答案 1 :(得分:0)

该界限使方法不安全。不是对象安全的特征不能用作类型。

  

Self作为参数,返回Self或其他要求Self: Sized的方法不是对象安全的。这是因为特征对象上的方法是通过动态调度调用的,并且在编译时无法知道特征实现的大小。 -Peter Hall

引用official docs

  

仅可以将对象安全的特征制成特征对象。如果这两个条件都成立,那么特征是对象安全的:

     
      
  • 特征不需要Self: Sized
  •   
  • 所有方法都是对象安全的
  •   
     

那么,什么使方法成为对象安全的呢?每种方法都必须要求Self: Sized或以下所有条件:

     
      
  • 必须没有任何类型参数
  •   
  • 不得使用Self
  •   

另请参阅: