Skip to content

[RFC] Proposal for infix . associated/'static' method syntax #12358

Closed
@nrc

Description

@nrc

Background

Issues #6894 and #8888.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions