Description
Background
pnkfelix's blog post: http://blog.pnkfx.org/blog/2013/04/22/designing-syntax-for-associated-items-in-rust/
nmatsakis's blog posts: http://www.smallcultfollowing.com/babysteps/blog/2013/04/02/associated-items/ and http://www.smallcultfollowing.com/babysteps/blog/2013/04/03/associated-items-continued/
Proposal
Use dot syntax for associated items. We use a type before the dot to define which implementation to call and a path to a function (via traits) after the dot to define the fully qualified name of the method. E.g.,
Trait T1 {
fn f() -> Self;
}
Trait T2 {
fn f() -> Self;
}
fn g1<X: T1 + T2>() -> X {
X.T1::f()
}
Rationale
Use :: paths to indicate the name of an item only, i.e., the fully qualified name.
Use . paths to specify the implementation of the item called.
This intuition applies equally to regular methods and 'static' methods.
Syntax
(Just looking at methods for now)
regular methods:
e ::= e.name(...) | ...
Note that we're using the dynamic type of e, whereas for static methods we use the static type.
static methods:
e ::= T.name(...) | ...
where:
T ::= [the usual type syntax]
name ::= (mod '::')* (T '::')? name_lit [i.e., usual path syntax]
Shorthands
If a name is un-ambiguous, it does not need qualification (this gives backwards compatability for non-static methods). This also gives a nice syntax for the common use case - the T::size_of()
example is common, which here would be T.size_of()
E.g.,
fn g2<X: T1>() -> X {
X.f()
}
If the implementing type is unambiguous (e.g., from the return type) then the target is optional (this gives backwards compatability with static methods). E.g,
fn g3<X: T1 + T2>() -> X {
T1::f()
}
By applying both shorthands, the following is allowed, which I don't like - it seems too magical. But I think it is not, so I could live with it.
fn g4<X: T1>() -> X {
f()
}
(Personally, I do not like inference relying on the return type so would be happy to see it go and thus avoid the 'magical' example. But that would break backwards compatibility and it is just a personal preference, let's ignore this possibility for now).
Generalising to associated items
(note: this section is post-1.0 stuff)
If traits can declare types, etc. as well as just methods we should be able to refer to them in same way, e.g.,
Trait Graph {
type Node;
}
fn g1<X: Graph>(n: X.Graph::Node) {
...
}
fn g2<X: Graph>(n: X.Node) {
...
}
Note that this needs a lot more thinking about wrt semantics and especially inheritance (see literature on virtual types, virtual classes).
Issues
Accessing functions from struct impls not in traits - I don't think this is an issue since you wouldn't use a struct as a bound on a type parameter.
grep-ability - not great because of the shorthands you can not grep to find all uses. But that is the price of shorthands. A semantic tool like DXR would still find everything.
Do we allow .
in types? I can't think of a use. If so (perhaps associated types using this proposed syntax is an example) we would require wrapping the type in parentheses or something.
Hat-tips
bjz for the idea of using .
nmatsakis/pcwalton for the T::<for X>::f()
syntax of which this is a variation.
pnkfelix/nmatsakis for the explanations