Trying to understand dyn trait limitations

Dyn trait cannot be made into object

This code causes a compiler error of error[E0038]: the trait `Test` cannot be made into an object

pub trait Test {
    fn static_fn();
}

impl Test for u8 {
    fn static_fn() {
        println!("Static Fn Called");
    }
}

fn test_dyn_trait(a: &dyn Test) {
    Test::static_fn();
}

fn main() {
    test_dyn_trait(&10);
}

I don't understand the limitation with static functions on dyn Trait's. My understanding of a dynamic trait is it passes a function pointer table of the trait functions alongside the object. I don't see how a static function cannot be added to this function pointer table.

You cannot add two dyn Traits to one parameter

This may have a similar reasoning to my first question, but you cannot provide multiple traits to a dyn Trait parameter without it being an autotrait. For example...

use std::fmt::Display;

pub trait Test {
    fn test(&self);
}

impl Test for u8 {
    fn test(&self) { }
}

fn test_dyn_trait(a: &dyn Test+Display) {
    a.test();
}

fn main() {
    test_dyn_trait(&10);
}

To me the declaration of fn test_dyn_trait(a: &dyn Test+Display) describes a function which takes a reference to an object, a function pointer table for trait Test, and a function pointer table for trait Display. I don't see why you would need to make Display a supertrait of Test for this code to be valid.

Thank you for your help

This was discussed recently:

1 Like

As for the second limitation, IIRC it's merely a implementation shortcoming, and work is being done to fix it. I'm not sure what exactly the shortcoming is, though.

1 Like

Which vtable does this use?

fn example() {
    <dyn Trait>::method();
}

You need some order-agnostic strategy for combining vtables, be it super-wide pointers[1] or vtable chaining or something else. I think there are also other concerns about

  • finding a performant way to do that
  • making dyn This + That + Kitchen + Sink the path of least resistance
  • simultaneously supporting or being forward-compatible with upcasting to any subset

You can work around it by making your own subtrait and blanket-implementing it.

pub trait TestDipslay: Test + Display {}
impl<T: ?Sized + Test + Display> TestDisplay for T {}

At some point you'll be able to upcast to supertraits too.[2]


  1. Rust pointers are currently always one or two usize big ↩︎

  2. Or you can do it "manually" already. ↩︎

4 Likes

Sorry, not quite sure how to quote subsections, but for

Which vtable does this use
To me this would not be valid code unless the the function was as follows. Whoever calls the function would then need to provide the function pointer table for Trait as the first parameter.

fn example<T: Trait>() {
    T::method();
}

From the rest of your post, what I understand is it is possible, but would be significant effort. Is that a correct interpretation?

Thank you all for your help

If you know the implementing type, you don't need the vtable, you can call the implemented method directly. And if you need to know the implementing type, it doesn't need to be part of the vtable at all, since dyn Trait doesn't know its own implementing type and wouldn't be able to call it.

And this is supported.[1]

Yes, it's possible, but we don't have it. You can emulate it to an extent. I don't know how amenable the teams are to adding it. I don't think there's any group focused on it right now.


  1. Using Sized for this is hacky, but that's how it works. ↩︎

4 Likes