[rust-dev] RFC: "impl Trait for Type"

Patrick Walton pwalton at mozilla.com
Sun Dec 30 20:35:52 PST 2012


There's recently been some evidence that the current state of affairs 
regarding where methods go is confusing; I think that our current impl 
syntax is resulting in some confusion. In particular, the difference I'm 
concerned about is this one (which I've explained on my blog [1]).

This code compiles:

     struct Foo {
         x: int,
         y: int
     }

     impl Foo {
         static fn new() -> Foo {
             Foo { x: 1, y: 2 }
         }
     }

     fn main() {
         let x = Foo::new();
     }

But this code does not:

     trait Constructible {
         static fn new() -> self;
     }

     struct Foo { ... /* as before */ ... }

     impl Foo : Constructible {
         static fn new() -> Foo {
             Foo { x: 1, y: 2 }
         }
     }

     fn main() {
         let x = Foo::new();
     }

Can you spot the bug? It's that the latter `impl` (with the `:`) is a 
trait implementation, not a type implementation, so it does not define a 
new name. Therefore, the call to `Foo::new()` does not resolve. With 
trait implementations, the methods it defines are scoped to the item on 
the *right-hand* side of the `:`, not the left-hand side. In this 
example, they're scoped to `Constructible`.

The way to fix this is to change the code to:

     fn main() {
         let x = Constructible::new();
     }

This state of affairs strikes me as confusing to C++/Java/C# users, who 
might be expecting the methods to be available in `Foo` as well as the 
`Constructible` interface. Indeed this was the case in earlier versions 
of Rust (and is the case today in some cases), but I worry that this 
feature is dangerous, as we might want our type to later implement a 
different typeclass with a different method called `new` and there would 
then be a name clash.

I also did a small survey of programming language syntax. Of the 
languages I'm aware of with typeclasses, here are the syntaxes I've seen 
for typeclass implementations:

* Haskell: `instance Typeclass Type where ...`

* C++1x concepts proposal: `concept_map Concept<Type> { ... }`

* Roy: `instance name = Typeclass Type { ... }`

* Felix: `instance Typeclass[Type] { ... }`

* Clojure: `(extend-type Type PROTOCOL ...)`

With the exception of Clojure, the languages seem to weigh heavily in 
favor of putting the typeclass before the type. Again, this makes sense 
to me, because the typeclass is where the methods are scoped to.

A while back, Niko proposed `impl Trait for Type`, which seems popular, 
based on an informal survey of the IRC channel. I like its clarity as 
well. With this change, the non-compiling example above would read this way:

     trait Constructible {
         static fn new() -> self;
     }

     struct Foo { ... /* as before */ ... }

     impl Constructible for Foo {
         static fn new() -> Foo {
             Foo { x: 1, y: 2 }
         }
     }

     fn main() {
         let x = Foo::new(); // ERROR unresolved name
     }

Now it seems easier to understand why this does not compile: the `new()` 
method is part of `Constructible`, not part of `Foo`. Because 
`Constructible` is on the left, it doesn't look like we've placed the 
`new` method in the `Foo` namespace.

I understand that there was an objection last time this syntax was 
brought up that our trait syntax in generics is `T:Trait`, and therefore 
it's more consistent to say `impl T : Trait`. However, it seems to me 
that the situation back then is different from the situation now in 
these ways:

1. It's no longer possible to implement more than one trait per `impl` 
declaration. This means that there will always be a difference between 
the generic syntax and the `impl` syntax no matter what we do: the 
generic syntax supports multiple traits, while the `impl` syntax 
supports just one.

2. The consequences of the programmer not understanding where the 
methods go are more severe now. Compared to the situation in the past, 
the programmer is now more likely to notice the difference between 
scoping the methods to the trait and scoping the methods to the type for 
two reasons:

(a) As noted before, I don't think that it'll work long-term to 
automatically make methods on trait implementations accessible without 
importing the trait, as Rust does today for non-static methods defined 
in the same module as the type. Besides the fact that I've always 
worried that this little-known rule is too subtle (did anybody else know 
about this?), it'll cause problems if I implement a method named `foo` 
on trait `A`, code starts to depend on calling `foo` without importing 
`A`, and then later on I implement a different method called `foo` on 
trait `B`; it'll break the original code.

(b) We have static methods now, and they don't leak out of their 
containing scopes; as such, they must be explicitly qualified with the 
trait that they belong to. This means that the scope that each method 
belongs to is now very apparent to the programmer and can't be swept 
under the rug.

So I'd like to consider changing the syntax for trait implementations 
only to `impl Trait for Type { ... }` for Rust 0.6. I understand that 
this is a late-stage proposal, and for that I apologize. But I worry 
more about the potential for confusion, as typeclasses are an uncommon 
feature in languages to begin with. Above all, I'd like to know whether 
others agree that this is confusing, and whether others think the new 
syntax is an improvement.

Thanks,
Patrick

[1]: http://pcwalton.github.com/blog/2012/12/30/the-two-meanings-of-impl/


More information about the Rust-dev mailing list