Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
nrc opened this issue Feb 18, 2014 · 22 comments
Closed

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

nrc opened this issue Feb 18, 2014 · 22 comments

Comments

@nrc
Copy link
Member

nrc commented Feb 18, 2014

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

@huonw
Copy link
Member

huonw commented Feb 18, 2014

General point: f.T::g (calling a static method g from trait T) is fairly similar to f.T::<g> (calling a normal method T with type parameter g).

@nrc
Copy link
Member Author

nrc commented Feb 18, 2014

Under this proposal, these would be (in full form) U.T::g() and e.T::g<U>() where the method is g either way, T is a trait, g is a method name, and U is a type. In both cases T would usually be omitted.

@huonw
Copy link
Member

huonw commented Feb 18, 2014

The latter one was calling a normal method.

struct Foo;
struct g;
impl Foo { fn T<A>(&self); {} }

fn main() {
    let f = Foo;
    f.T::<g>();
}

i.e. things with extremely unconventional names.

(I only brought it up because I thought the similarity is worth some consideration, I think it will be rare for it to be a point of confusion in practice. In any case, this proposal seems reasonable to me.)

@nrc
Copy link
Member Author

nrc commented Feb 18, 2014

Is f.T::<g>() existing syntax? I would have expected f.T<g>().

@cartazio
Copy link
Contributor

for some reason the way I"m reading this makes me think of the "path dependent types" in scala. Is that resemblance accidental / deliberate / me being tired and way too caffeinated?

@huonw
Copy link
Member

huonw commented Feb 18, 2014

@nick29581 yes, f.T<g>() could be (f.T < g) > () or (what is now) f.T::<g>(), and so the extra :: avoids the need for a type & expression cover grammar (and the need to use resolution information to disambiguate a parse). (I.e. avoids (some of) the problems C++ has.)

@nrc
Copy link
Member Author

nrc commented Feb 18, 2014

@cartazio Kinda similar - Scala's path dependent types are virtual types and are what you get if you allow associated types to interact with inheritance in fun ways. I'm not proposing that here, but there is a similarity and when we think seriously about full associated items we will need to consider a lot of the issues Scala faced.

@nrc
Copy link
Member Author

nrc commented Feb 18, 2014

@huonw I see, thanks. I will ponder how that fits in with this proposal. At first blush, I think there is no ambiguity.

More generally, I'm OK with virtual method calls and static method calls looking similar because they are similar. I hope the case conventions are enough to avoid confusion when reading. Not sure how others feel about that.

@cartazio
Copy link
Contributor

ok. Well my 2 cents is "evade emulating scala, pleaseeeeee". scala has a lot of unanticipated complexity in its types.

@pcwalton
Copy link
Contributor

Note that types and values are in different namespaces so T.v will not be backwards compatible if there is a value T in scope. But we could just start placing a placeholder name T in the value namespace whenever we see an associated item.

@cartazio This is not related to path dependent types, it's just a very explicit syntax for the same stuff that's in Haskell 98.

@pnkfelix
Copy link
Member

On first blush, I like the idea.

My initial concerns are that the more verbose S::<for T> syntax is more self-documenting and also it may be easier to produce good error messages on parse errors for the more verbose form.

But I do prefer my paths to be free of space characters, which thìs offers.

(I continue to believe there may be value in a shorthand for naming the impl (I.e. type/trait pair), e.g. if one is dealing with a long trait name. But that is an exceptional case for now.)

@Thiez
Copy link
Contributor

Thiez commented Feb 18, 2014

I prefer the S::<for T> syntax because I think it is easier to understand. Your proposal is shorter but I expect we don't need this syntax very often, so I think ease of understanding is more important than making it slightly less verbose.

@pnkfelix
Copy link
Member

(updated title to "Proposal for infix . associated/'static' method syntax" to reflect how this differs from the other issues regarding associated items.)

@cartazio
Copy link
Contributor

@pcwalton ok cool, i'll stare at this more and try to understand it properly

@cartazio
Copy link
Contributor

ok, I think i like this. Would this also allow sort of associated data families (eventually?)

eg If I wanted to write a Struct of Arrays trait that have an array data type parameterized by the element typed, would i be able to express that (eventually) with this proposal?

@nrc
Copy link
Member Author

nrc commented Feb 19, 2014

I'm not quite sure what you are proposing, but I think so. However, it would depend on how the eventual discussion on associated types pans out.

@cartazio
Copy link
Contributor

reasonable, i'm not sure how it'd translate to rust, but I'm thinking something that would make something analogous to the Struct of Arrays format supported by haskell would be nice

eg see http://hackage.haskell.org/package/vector-0.10.9.1/docs/Data-Vector-Unboxed-Mutable.html and the associated source http://hackage.haskell.org/package/vector-0.10.9.1/docs/src/Data-Vector-Unboxed-Base.html#Unbox and https://github.com/haskell/vector/blob/master/internal/unbox-tuple-instances

@liigo
Copy link
Contributor

liigo commented Feb 19, 2014

Will change to X::T1.f, if #12390 is addressed.

@pnkfelix
Copy link
Member

@liigo how does that follow? The scope of #12390 is restricted to use items, not paths in expression contexts, which is the only place the Trait::<for Type>::f syntax under discussion can occur.

(edit: above was written at a time when the scope of #12390 was restricted to use items)

@liigo
Copy link
Contributor

liigo commented Feb 19, 2014

#12390 was updated to path syntax

@nikomatsakis
Copy link
Contributor

I echo @pcwalton's concern about the namespaces.

@nrc
Copy link
Member Author

nrc commented Mar 16, 2014

This is superseded by the UFCS RFC (rust-lang/rfcs#4)

@nrc nrc closed this as completed Mar 16, 2014
bors added a commit to rust-lang-ci/rust that referenced this issue Jul 25, 2022
minor: Simplify syntax-highlighting macro checks
flip1995 pushed a commit to flip1995/rust that referenced this issue Mar 7, 2024
The following code used to trigger the lint:
```rs
 macro_rules! make_closure {
     () => {
         (|| {})
     };
 }
 make_closure!()();
```
The lint would suggest to replace `make_closure!()()` with
`make_closure!()`, which changes the code and removes the call to the
closure from the macro. This commit fixes that.

Fixes rust-lang#12358
flip1995 pushed a commit to flip1995/rust that referenced this issue Mar 7, 2024
[`redundant_closure_call`]: Don't lint if closure origins from a macro

The following code used to trigger the lint:
```rs
 macro_rules! make_closure {
     () => {
         (|| {})
     };
 }
 make_closure!()();
```
The lint would suggest to replace `make_closure!()()` with `make_closure!()`, which changes the code and removes the call to the closure from the macro. This commit fixes that.

Fixes rust-lang#12358

----

changelog: [`redundant_closure_call`]: If `x!()` returns a closure, don't suggest replacing `x!()()` with `x!()`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants