What is purpose of only one base trait when making trait objects?

Is something in language forbid using more base traits? I understand that I can create new trait that impl all other traits but it's a lot of additional work.

Rust doesn't have base traits or inheritance. The syntax trait Foo: Bar means "when implementing Foo, you also need to implement Bar", but they remain two separate traits. This is especially evident when you can't cast dyn Foo to dyn Bar.

You can make your trait require implementation of any number of other traits, e.g.:

use std::fmt::*;
trait VeryDisplay: Display + Debug {}
trait A {}

trait B {}

trait AB: A + B {}

fn main() {
    let b1: Box<dyn A + B>; // error
    let b2: Box<dyn AB>; // good
    let b3: Box<dyn A + Sync>; // using auto-triat is also good
}

I can't create b1. So why I can use only one non-auto-trait here?

That's because the syntax isn't Trait: SuperTraits which would specify some sort of trait relationship or inheritance. It's Trait: Bounds, which is an abbreviation for a Trait where Self: Bounds clause that specifies requirements for objects that implement the trait.

The bounds don't even have to be traits. This is also valid:

trait Foo: 'static {}

but you can't make Box<dyn 'static> as that wouldn't make sense. Box<dyn A + 'static> does work though (similarly to auto traits). I'm not sure if there's a deeper justification for that other than lifetime bounds and auto-traits are useful to have there.

1 Like

As to why dyn A + B doesn't work, well, it's simple: nobody knows how it ought to work.

Recall that Box<dyn Trait> comprises two pointers: a data pointer and a vtable pointer. There's at least two things that Box<dyn A + B> could mean:

  • A data pointer + vtable pointer, where the vtable is a "merged" vtable containing all the methods of both A and B
  • A data pointer + vtable pointer for A + vtable pointer for B.

The first thing has problems that have to do with how the vtables are merged, whether Box<dyn A + B> is the same type as Box<dyn B + A>, whether you can upcast to Box<dyn A>, and the explosion of vtables that might need to be automatically generated if you use this feature a lot with different types.

The second thing is neater in a way, but it breaks the assumption that all Box<T>s are either one pointer (if T: Sized) or two (if T: !Sized).

Personally I'm in favor of the second interpretation, since it is not redundant with something you can already do in Rust (the combined trait AB). Especially if we one day get supertrait coercion for trait objects I don't think it would be worth it to add dyn A + B that just automatically creates dyn AB; I'd rather leave dyn A + B as syntax that allows for something that isn't otherwise possible.

But there has been (AFAIK) no official decision on whether Rust will adopt one of these meanings, and if one of them is chosen there will still be a lot of work to implement and stabilize it.

1 Like