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

Reply via email to