'Implementation Generics', A Potential Solution to the Orphan Rules

Alright, so this is a new idea and not at all fleshed out and I would love for criticism. I am not a compiler dev so this discussion is about design, not implementation.

The solution I am proposing would treat trait implementations in a similar way to how we already interact with generics. We now define a types implementation signature with one of these possible syntax options (haven't decided what's best):

MyType<T,U>|OtherTrait from other_crate|

or

MyType<T,U, OtherTrait from other_crate>

or

MyType<T,U, path::to::named::impl>

Omitting the implementation (ie. using the regular type signature) would mean that the behaviour would be identical to the current operation. If the trait is required and is not implemented for the type under the current orphan rules then we get a comp error.

Under the hood, the compiler is treating each of these items as distinct types (similar to what we're manually doing today). Function signatures that use these types don't need to change. They don't care about the implementation details since different implementations are actually just distinct types.

We can have explicit conversions between implementations like this:

let x = MyType::new(); // implementation is unspecified so we use defaults
let y = x as MyType|OtherTrait from better_crate|;

I know there have been other new type proposals but this has one main advantage. With this system there is no fragmentation of types. MyType is still a MyType. Functions can directly accept MyType and the compiler can keep track of it's implementation signature. To clarify, fn(MyType) would work out of the box with both MyType|OtherTrait from other_crate| and MyType|OtherTrait from better_crate|. All functions are automatically generic over a types trait implementations.

This could then interact with type aliases to make things look a little nicer.

type MyModifiedType = MyType|OtherTrait from other_crate|;

I think something like this could massively improve code reusability. I'd love to hear about what you guys think.

There's also the question of syntax. I don't mind what I've suggested above but there are lots of good options. We could introduce named impls

CustomTrait impl Trait for MyType<T,U> { .. }

and then the signature looks like this

MyType<T, U, path::to::CustomTrait>

This would be the most in line with the current language, but perhaps less clear? Maybe maybe not. Not sure how ordering would work with multiple reimplementations? perhaps it could be order agnostic.

Perhaps the impl could share the name of the Trait by default so you could just have:

impl Trait for MyType<T,U> { .. }

and then the signature looks like this

MyType<T, U, path::to::Trait>

or

MyType<T, U>|path::to::Trait|

or

MyType<T, U>|Trait from path::to::Trait|

I achieve this with a defaulted type argument:

pub trait MetricSpace<UserImplementationType = ()>
3 Likes