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