Just tossing this out here in case anyone wants to tackle it.
It should be very easy to make dyn SubTrait be a subtype of dyn SuperTrait where SuperTrait is the only non-marker trait that SubTrait extends (it single-inherits from it, ignoring markers).
When this happens we can layout the vtable of SubTrait so that the SuperTrait vtable is a prefix of it, meaning no runtime coercions are required. I wouldn’t be surprised if this already worked by accident, since that’s the obvious way to layout vtables anyway.
I have been told that previous attempts at trait subtyping were always more ambitious than this and subsequently got bogged down in details. But this should be completely compatible with any future extensions without too much work.
It does lock us into guaranteeing that property of vtable layout but that seems fine?
Subtyping is maybe a step too controversial as it greatly extends the scope of subtyping in Rust (currently it’s only about lifetimes). Coercions, or “upcasting” of trait object pointers, would be a more minimal first step that would allow a lot of things, just not reinterpretation of e.g. &[&dyn SubTrait] as &[&dyn SuperTrait]. (This was discussed in discord, bringing it here for completeness.)
Just throwing out an idea that adds a minimal amount of syntax to make the compatibility obvious: opt-in to the guarantee. (This could then be made automatic in the future if it’s found not to cause issues.)
That’s the idea; make it opt in such that it’s more obviously handled. (The specification could also scale to other cases where it needs to be specified, but that’s not the point currently.)
I think this idea has a lot of potential. However, I also think that this is by no means an obvious or simple change.
Extending the subtyping system is nontrivial, potentially affecting what guarantees unsafe code may rely on. Also, Rust has historically favoured composition over inheritance.
Therefore, even though I like the proposal, I think we should explore its benefits and drawbacks more carefully. I also think that the impact of this potential change warrants an RFC.
I am also in favour of making this opt-in. Because subtyping is subtle yet powerful, I think this behaviour should be explicit.
Your syntax (which I think is better specified as an attribute trait T: #[super] K… though I don’t believe we allow attributes in type bound position right now) has another bonus: we can inherit from many non-marker traits, but the super trait can still be guaranteed to be the vtable prefix (this is similar to e.g. Scala where a class may inherit from only one class but from many traits).
How well does trait solving work in the face of subtyping? Neither Haskell or Rust has any true form of subtyping (lifetimes don’t count), and that’s the limits of my experience.
I’m thinking of things like:
// have some combination of
impl Trait for dyn Base { ... }
// and/or
impl Trait for dyn Derived { ... }
and how subtyping will play a role in searching for impls of Trait for dyn Derived.
I get the overall motivation for wanting to use subtyping; the coercions defined in rust are pretty limited and can’t reach deeply nested type parameters. The novelty just has me concerned!