[rust-dev] RFC: Rework generic paths

Patrick Walton pwalton at mozilla.com
Sun Apr 28 10:38:35 PDT 2013


Hi everyone,

The reactions to this bug on impls [1] have caused me to think that the 
current treatment of paths in generic type and trait implementations is 
something of a wart and perhaps should be reworked. Specifically, the 
problem is that this:

     impl<T> MyType<T> {
         fn new<U>() -> MyType<T> { ... }
     }

Cannot be accessed (as you might expect) like so:

     MyType::<int>::new::<float>()

But instead you must concatenate the type parameters like this:

     MyType::new::<int,float>()

This is highly unintuitive.

Basically, all of this is an artifact of the fact that, internally, we 
treat a type implementation as essentially a module, and modules don't 
have type parameters. This fact also brings with it an unfortunate issue 
relating to typedefs, namely that you cannot call static methods through 
them. You might wish to write:

     type IntMyType = MyType::<int>;

     MyIntType::new::<float>()

But you can't. In fact, you can't call static methods *at all* through 
typedefs, meaning that this doesn't work:

     impl MyOtherType {
         fn new() -> MyOtherType { ... }
     }

     type Alias = MyOtherType;

     Alias::new() // doesn't work

This severely reduces the utility of typedefs.

I've been thinking about ways we could fix this without severely 
complicating the compiler, and I think I've got the sketch of an idea 
that might work. I propose that we change type implementations (and 
traits) to be less module-like in the following ways:

1. Forbid `use` statements from importing from type implementations or 
traits. Having `use` statements take type parameters would severely 
complicate an already complex resolution pass, and might even require 
the name resolution and typechecking phases to be intertwined in an 
incoherent way.

2. Add a new type of `path` to the grammar:

     '::'? identifier ('::' identifier)* '::' '<' type_parameters '>'
     '::' identifier ('::' identifier)* ('::' '<' type_parameters '>')?

This production admits paths like `MyType::<int>::new::<float>()`.

3. Internally, whenever the resolve pass sees one of these paths, or 
sees a component in a pass resolve to a typedef, it drops the type 
parameters and follows any typedefs to find the associated `impl` or trait.

4. When the typechecker sees such a path, it resolves the type portion 
of that path. If the type didn't resolve to a nominal monotype, it 
reports an error. Otherwise, it pulls the type parameters out of that 
nominal type and concatenates them with the type parameters supplied in 
the rest of the path, if any, to produce the "real" set of type 
parameters used by typechecking and translation.

These semantics should allow all type parameters to be dropped if they 
can be inferred, as today, but should allow typedefs to "just work". 
Unless I'm missing something. :)

This is not a backwards compatible change, but I don't expect much code 
to be impacted: I suspect `use` from a trait or type implementation is 
rare, and concatenated type parameters even more so.

Thoughts?

Patrick

[1]: https://github.com/mozilla/rust/pull/6087


More information about the Rust-dev mailing list