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

Reply via email to