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
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev