8

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:15:11
   |
15 |     boxed.baz();
   |           ^^^

Why is this not possible? It works when I remove the Self: Sized bound, but then I can't use generics which make the function more comfortable for the caller.

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

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Tim Diekmann
  • 7,755
  • 11
  • 41
  • 69

2 Answers2

7

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 parameter.

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 {

sunside
  • 8,069
  • 9
  • 51
  • 74
Phoenix
  • 1,553
  • 2
  • 13
  • 29
  • *It sounds like you're asking why the `: Sized` restriction is necessary.*: No: *I'm not asking, why the bound is required.* – Tim Diekmann Aug 15 '18 at 06:23
  • However, I wasn't aware of that a `: Sized` bound makes a trait *not object safe*. You are basically answering the linked question again. – Tim Diekmann Aug 15 '18 at 06:26
  • "This question asks about, why you can't call baz from a trait object." You can't call baz from a trait object because baz is a generic function and you cannot call generic functions from trait objects. – Phoenix Aug 15 '18 at 19:56
  • So what would be the way to "solve" this so that this Bar trait becomes callable ? – Benoît Nov 10 '19 at 11:13
  • 1
    @Benoît that's a very non-trivial question, but in this specific example, you'd removed the `where Self: Sized` bound, then necessarily remove all generics from the method. Here, the generic is the `impl AsRef`, which is kind of secretly a type parameter. Here, you could simply replace it with `&str`. – Phoenix Nov 10 '19 at 18:13
1

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:

Community
  • 1
  • 1
Tim Diekmann
  • 7,755
  • 11
  • 41
  • 69