Looking for RFC coauthors on "named impls"


#1

I’m planning to write an RFC on named impls and I am looking for collaborators on this RFC. If this interests you and you want to reach out for more direct conversation - you can find me on #rust-lang @ irc.mozilla.org (CET timezone mostly).

What are named impls?

Consider a Monoid trait. For usize there are more than one valid monoids:

  • (usize, 1, *)
  • (usize, 0, +)

But which do we impl for? Well - what if we didn’t have to pick? With named impls, you can do:

mod my_mod {
extern crate frunk;

use frunk::monoid::Monoid;

impl Sum of Monoid for usize { .. }
impl Product of Monoid for usize { .. }

pub use Sum as _;  // Reexports Sum as anonymous (which are all hitherto valid impls).
}

In another module you may then:

use my_mod::Product; // Imports Product into the module scope.

fn main() {
   let x = <usize as my_mod::Sum>::combine(1, 2); // UFCS
   let x = <usize as my_mod::Sum of Monoid>::combine(1, 2);  // Equivalent
   let x = usize::combine(1, 2) // refers to <usize as my_mod::Product>::combine(1, 2);
}

Named impls must be referred to explicitly (including via UFCS or importing explicitly), therefore there should be no backwards compatibility issues. The named impls feature is also a sound (hopefully) escape hatch out of the orphan rule.

There are plenty of issues and kinks to work out, including:

  • is this always sound?
  • how do we deal with trait objects?
  • what is the syntax when generics are involved both for UFCS and defining impls.
  • what is the syntax for associated items?
  • how do we make this maximally ergonomic?

Thus I can’t promise that we will solve everything and make an RFC PR. However, it will be a fun experience =)


Opt-in implementations and alternative solutions
#2

This could be “sugar” for this:

trait Monoid<T> { .. {

struct Sum;
struct Product;

impl Monoid<Sum> for usize { .. }
impl Monoid<Product> for usize { .. }

In which case, it is sound, but - in my opinion - hard to justify as sugar when you can just do it with type parameters.

In contrast, if you are allowed to create named impls for traits which have not explicitly opted into having named impls, that is decidedly unsound. The common explanation for this is the “Hash table problem” - HashMap assumes that <usize as Hash> will resolve to the same impl in every context. If it doesn’t our HashMap is unsound.

That is to say that coherence is an invariant that unsafe code is allowed to assume. Given the same set of input types, a trait instantiation will always resolve to the same impl. If a “name” is just another input parameter, then that’s fine, but again, not more expressive than what exists today.

You could search “named impls” on this forum and find previous discussions.


#3

With the sugar idea, is the trait “copied” or does the original trait have to support a name parameter? The sugar idea is nice - and of course it is not more expressive then what exists today - but I think it would be more ergonomic than creating a bunch of newtypes and then impl:ing all the traits the base type had save for one.

With GeneralizedNewtypeDeriving (Haskell) newtypes would be less unergonomic, but it still incurs a lot of boilerplate.