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: Require explicit syntax to declare boxed, unique closures #1864

Closed
nikomatsakis opened this issue Feb 17, 2012 · 11 comments
Closed

RFC: Require explicit syntax to declare boxed, unique closures #1864

nikomatsakis opened this issue Feb 17, 2012 · 11 comments
Assignees

Comments

@nikomatsakis
Copy link
Contributor

So, currently, the sugared closure syntax {||...} can be used for any kind of closure. @brson and I wanted this so that task spawning looks nice:

task::spawn {||...}

but I've since soured on it (and I think he has too). The basic reason is that it is not explicit whether the closure is a boxed or unique closure: this depends on the definition of the callee function. But this distinction is important to the semantics and cost model. Therefore, I would like to make it so that {||...} is always a stack closure (fn&).

With no other changes, this would mean that spawning a task is written task::spawn(fn~() {...}). To make fn@() and fn~() a little more convenient to use, however, we could make any or all of the following changes:

  1. Do not require variable types (I think this would be relatively straightforward; I also think there's an existing bug for this but can't find it at the moment).
  2. Allow parentheses to be omitted if there are no arguments (e.g., task::spawn(fn~ {...}))
  3. Allow paren-less calls, similar to a sugared closure like {||...} (e.g., task::spawn fn~ {...} or task::spawn fn~() {...})

This RFC is truly a "request for comment" because I'm not really sure what's best. Should we just leave things as they are? If not, do we implement the proposals above to lighten the syntax a bit? I think the first point (omitting variable types) is a clear win, but I'm less sure about #2 and #3. The fully explicit form is perhaps not so bad.

p.s. Another option might be to add some sigil to the sugared form, but task::spawn {~|| ...} just looks like a lot of random characters in a row to me.

@marijnh
Copy link
Contributor

marijnh commented Feb 17, 2012

How about arrow-based syntax? That could look nice enough to also replace {||}-style closures. I think this is easy enough to parse (though not LL(1)). Rough proposal:

(a, b) -> {/*body*/} // This is a fn&
(a, b) ~> {...} // a fn~
(x) @> {...} // For fn@ ... Might be a bit too ugly. => might be better
-> {...} // equivalent to () -> {...}

@bstrie
Copy link
Contributor

bstrie commented Feb 17, 2012

I concur that the first point is a clear win; it's important to acknowledge that there are times when people simply don't care to specify the argument and return types of their closures.

+1 to allowing parens to be omitted when there are no arguments. This is really no different from omitting the -> () in the absence of a returned value. (Wishful thinking: perhaps this could even apply to functions declared in the usual sense?)

I think that paren-less calls for trailing closures are one of Rust's "hey, that's pretty neat" features, but I'm not especially attached to them (though I'd be a little sad to see them go). However this one is decided, the stack closure syntax should work the same way.

@brson
Copy link
Contributor

brson commented Feb 17, 2012

I really like that the arrow syntax has a natural place to stuff the kind sigil, but combined with the sugar for trailing lambdas it is pretty confusing.

spawn_with_ctxt (ctxt) ~> {
    ...
};

@graydon
Copy link
Contributor

graydon commented Feb 17, 2012

I actually kinda like the block form we have now, since it makes both the parameter names and the curly-open wind up on the line with the caller. Makes "pass a closure" read more like built-in control forms. That's its charm, anyway. But the argument about explicit-ness is fair; just off the cuff, how about supporting sigils in there?

{&|a, b| ... }   // explicit fn&
{@|a, b| ...}    // explicit fn@
{~|a, b| ... }   // explicit fn~
{|a, b| ...}     // implicit, infer by callee

I also agree some with @Wensleydale that I'm ... often ok letting it be inferred (because I'm not planning on doing any environment capture, or nothing that would notice any difference)

@nikomatsakis
Copy link
Contributor Author

@brson and I were tossing around a variant on @marijnh's arrow syntax. Something like this:

{ a, b -> ... } (stack closure)
{ a, b @> ... } (boxed closure)
{ a, b ~> ... } (unique closure)

In use it would look like:

vec::iter(v) { i -> 
    ...
}

With no arguments, the arrow is still required:

task::spawn { ~>
    ...
}

There are some problems:

  • There is a minor inconsistency in that stack closures ought to be &> but that just looks so...weird (especially as it is by far the common case).
  • We need arbitrary lookahead. So we have to write some little routine in the parser that skips forward to see if it can find an arrow or a ':' (the latter would indicate a type annotation on a parameter, and is not valid expression syntax). In practice this is probably not a huge deal.
  • If we want to allow explicit return type annotation, it's not clear how to do that, since we've stolen the "return type arrow" (but I think this is probably not a requirement).
  • It no longer looks like Ruby or Smalltalk.

@nikomatsakis
Copy link
Contributor Author

@graydon also explicit sigils might be ok. Mostly I was afraid of this:

task::spawn {~||
    ...
}

where the {~|| can be fairly characterized as line noise.

@graydon
Copy link
Contributor

graydon commented Feb 24, 2012

Surely task::spawn infers the ~ most of the time, no?

The arbitrary lookahead scanning the argument list seems like a pretty big negative concerning arrows, to me. I realize this is "arguing over syntax", and am certainly willing to talk through further options; but I'd prefer the parser gets simpler over time (as we figure out nice ways to regularize and formalize what it's doing) rather than more complex.

@brson
Copy link
Contributor

brson commented Feb 24, 2012

@graydon - spawn always infers ~. The original goal if this issue was to remove that inference entirely, make it always explicit.

If we are going to keep the inference then adding the sigils is less important, though it probably will allow block syntax in places where we currently have to use the long syntax.

It's a shame that this will add yet more ways to write functions. If we want to keep the kind inference then I think my inclination is to leave the block syntax alone and make the long syntaxes easier to use by allowing both the argument types and return types to be omitted.

@nikomatsakis
Copy link
Contributor Author

Some thoughts on the matter:

  • When the work on mutable local variables is complete (working on it...) and the no copies proposal is implemented (not started yet), it will only be legal to implicitly copy immutable variables of simple types. This will allay some of the concerns about not understanding which kind of closure you're getting, as it should make no semantic difference, though the costs will vary some.
  • It seems like a no-brainer to allow types to be dropped from explicit fn() style declarations. I think this would be easy, I may try it off on a branch and see if any complications arise.
  • I had a thought about the capture clause syntax. Currently, it is fn@[copy x; move y](z: int). Perhaps the captured variables should come at the end of the parameter list: fn@(z: int; copy x; move y) {...}. This would fit nicely with the sugared closure syntax: {|z; copy x; move y|...}.

@nikomatsakis
Copy link
Contributor Author

Note that capture clauses will be needed more often if we stop implicitly copying non-trivial data structures (though the move cases ought to be implicit, once we get #1894 straightened out).

@ghost ghost assigned nikomatsakis Apr 12, 2012
@nikomatsakis
Copy link
Contributor Author

I'm closing this, as there has been no motion forward.

celinval pushed a commit to celinval/rust-dev that referenced this issue Jun 4, 2024
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

5 participants