Why a function on a trait object cannot be called

2020-02-14 05:27发布

I have the following code:

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

Which results in this error:

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

Why this is not possible? It's working when removing the Self: Sized bound, however, then I can't use generics, which is pretty comfortable for the caller.


This is not a duplicate of Why does a generic method inside a trait require trait object to be sized?. This question asks about, why you can't call baz from a trait object. I'm not asking, why the bound is required. This has already been discussed.

标签: rust
2条回答
叼着烟拽天下
2楼-- · 2020-02-14 05:57

The bound makes the method not object safe. Traits that are not object safe cannot be used as types.

Methods that take Self as an argument, return Self or otherwise require Self: Sized are not Object safe. That's because methods on a trait object are called via dynamic dispatch and the size of the trait implementation cannot be known at compile time. -- Peter Hall

Citing the official docs:

Only traits that are object-safe can be made into trait objects. A trait is object-safe if both of these are true:

  • the trait does not require that Self: Sized
  • all of its methods are object-safe

So what makes a method object-safe? Each method must require that Self: Sized or all of the following:

  • must not have any type parameters
  • must not use Self

See also:

查看更多
Explosion°爆炸
3楼-- · 2020-02-14 06:07

Because Rust's generics system works through monomorphization.

In Java, for example, type parameters in a generic function turn into variables of type Object, and are casted as necessary. Generics in languages like this simply serves as a tool to help verify the correctness of types within code.

Languages such as Rust and C++ use monomorphization for generics. For each combination of type parameters a generic function is invoked with, specialized machine code is generated which runs that function with those combinations of type parameters. The function is monomorphized. This allows data to be stored in place, eliminates the cost of casting, and allows the generic code to call "static" functions on that type paramameter.

So why can't you do that on a trait object?

Trait objects in many languages, including Rust, are implemented using a vtable. When you have some type of pointer to a trait object (raw, reference, Box, reference counter, etc.), it contains two pointers: the pointer to the data, and a pointer to a vtable entry. The vtable entry is a collection of function pointers, stored in an immutable memory region, which point to the implementation of that trait's methods. Thus, when you call a method on a trait object, it looks up the function pointer of the implementation in the vtable, and then makes an indirect jump to that pointer.

Unfortunately, the Rust compiler cannot monomorphize functions, if it does not know at compile time the code that implements the function, which is the case when you call a method on a trait object. For that reason, you cannot call a generic function (well, generic over types) on a trait object.

-Edit-

It sounds like you're asking why the : Sized restriction is necessary.

: Sized makes it so that the trait cannot be used as a trait object. I suppose there could be a couple of alternatives. Rust could implicitly make any trait with generic functions not object safe. Rust could also implicitly prevent generic functions from being called on trait objects.

However, Rust tries to be explicit with what the compiler is doing, which these implicit approaches would go against. Wouldn't it be confusing, anyways, for a beginner to try and call a generic function on a trait object and have it fail to compile?

Instead, Rust lets you explicitly make the entire trait not object safe

trait Foo: Sized {

Or explicitly make certain functions only available with static dispatch

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

查看更多
登录 后发表回答