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

Unboxed closures #114

Merged
merged 2 commits into from
Jul 30, 2014
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
386 changes: 386 additions & 0 deletions active/0000-closures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,386 @@
- Start Date: (fill me in with today's date, YYYY-MM-DD)
- RFC PR #: (leave this empty)
- Rust Issue #: (leave this empty)

# Summary

- Convert function call `a(b, ..., z)` into an overloadable operator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this have any impact on bare functions? Would they effectively implement FnShare?

via the traits `Fn<A,R>`, `FnShare<A,R>`, and `FnOnce<A,R>`, where `A`
is a tuple `(B, ..., Z)` of the types `B...Z` of the arguments
`b...z`, and `R` is the return type. The three traits differ in
their self argument (`&mut self` vs `&self` vs `self`).
- Remove the `proc` expression form and type.
- Remove the closure types (though the form lives on as syntactic
sugar, see below).
- Modify closure expressions to permit specifying by-reference vs
by-value capture and the receiver type:
- Specifying by-reference vs by-value closures:
- `ref |...| expr` indicates a closure that captures upvars from the
environment by reference. This is what closures do today and the
behavior will remain unchanged, other than requiring an explicit
keyword.
- `|...| expr` will therefore indicate a closure that captures upvars
from the environment by value. As usual, this is either a copy or
move depending on whether the type of the upvar implements `Copy`.
- Specifying receiver mode (orthogonal to capture mode above):
- `|a, b, c| expr` is equivalent to `|&mut: a, b, c| expr`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this default. It's neither the most relaxed bound for what you can do in bodies (that's FnOnce), nor is it the most relaxed bound for when you can call the closure (that's FnShare).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me neither. If we don't use something flexible, it will bite us in the back. And we already have a solution. One that we can actually implement relatively easily, too, not something impossible.

- `|&mut: ...| expr` indicates that the closure implements `Fn`
- `|&: ...| expr` indicates that the closure implements `FnShare`
- `|: a, b, c| expr` indicates that the closure implements `FnOnce`.
- Add syntactic sugar where `|T1, T2| -> R1` is translated to
a reference to one of the fn traits as follows:
- `|T1, ..., Tn| -> R` is translated to `Fn<(T1, ..., Tn), R>`
- `|&mut: T1, ..., Tn| -> R` is translated to `Fn<(T1, ..., Tn), R>`
- `|&: T1, ..., Tn| -> R` is translated to `FnShare<(T1, ..., Tn), R>`
- `|: T1, ..., Tn| -> R` is translated to `FnOnce<(T1, ..., Tn), R>`

One aspect of closures that this RFC does *not* describe is that we
must permit trait references to be universally quantified over regions
as closures are today. A description of this change is described below
under *Unresolved questions* and the details will come in a
forthcoming RFC.

# Motivation

Over time we have observed a very large number of possible use cases
for closures. The goal of this RFC is to create a unified closure
model that encompasses all of these use cases.

Specific goals (explained in more detail below):

1. Give control over inlining to users.
2. Support closures that bind by reference and closures that bind by value.
3. Support different means of accessing the closure environment,
corresponding to `self`, `&self`, and `&mut self` methods.

As a side benefit, though not a direct goal, the RFC reduces the
size/complexity of the language's core type system by unifying
closures and traits.

## The core idea: unifying closures and traits

The core idea of the RFC is to unify closures, procs, and
traits. There are a number of reasons to do this. First, it simplifies
the language, because closures, procs, and traits already served
similar roles and there was sometimes a lack of clarity about which
would be the appropriate choice. However, in addition, the unification
offers increased expressiveness and power, because traits are a more
generic model that gives users more control over optimization.

The basic idea is that function calls become an overridable operator.
Therefore, an expression like `a(...)` will be desugar into an
invocation of one of the following traits:

trait Fn<A,R> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a good suggestion for how these traits can inherit: #97 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not sure that we want a trait inheritance relationship. That would imply (e.g.) that one could implement those other traits differently, so that your function behaved in different ways. That might be useful in some cases but is also sort of surprising. Another option would be to include impls like:

impl<T:FnShare> FnOnce for T { ... }

and so forth. Unfortunately since coherence doesn't know that FnShare, Fn, and FnOnce are mutually exclusive that might run into some problems of its own.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't have a subtyping relationship on the closure traits, then we'll just end up with a needless variants of higher-order functions.

For example, if I want to write foo(f: ||) { f(); f(); } and I don't really care whether f mutates its environment or not, then I'd like to be able to pass both Fn<(),()> and FnShare<(),()> to foo. But I can't do that because there's no subtyping! Instead I'll need to provide foo_imm(f: |&:|) { f(); f(); } and foo_mut(f: |&mut:|) { f(); f(); }. If I only called f once in the body of foo, then I'd need to add a foo_once version as well to be as general as possible. If I have multiple closures involved, then I'll need to provide variants for every permutation of the relevant types. This gets out of hand very quickly.

Not having subtyping will either:

  • Create this entirely pointless explosion of variants
  • Encourage people to put an artificially restrictive Fn bound on an otherwise FnShareable closure because no one is bothering to write the FnShare variants. FnOnce would probably still be used due to its utility so we'd have the foo / foo_once explosion still.
  • No one bothers to make variants at all and we have to explain forevermore why you can't pass your closure to this function when it's perfectly safe to do so.

Yeah it's weird if people implement the traits with different bodies, but it's not any different than when people don't follow the rules for other kind of operator overloading. Slightly different bodies are actually necessary for a FnOnce instance versus Fn/FnShare because of the values vs refs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a serious issue with using trait inheritance, which is the vtable size. It must be kept to 1 method, otherwise performance will suffer (though, Fn and FnShare would use the same function for their methods, so something may be done with that in mind).

I wonder if what @anasazi is pointing out still holds if closure bodies were to implement either FnOnce, FnOnce + Fn or FnOnce + Fn + FnShare (instead of just one trait).

Encourage people to put an artificially restrictive Fn bound on an otherwise FnShareable closure ...

I believe not, if the above is the only remaining issue, because Fn wouldn't be restrictive, as all FnShare closures would also implement it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will always be pointer -> [destructor_pointer, method_pointer] anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately since coherence doesn't know that FnShare, Fn, and FnOnce are mutually exclusive that might run into some problems of its own.

Does #48 cover this at all?

(As much as it pains me to say, would it be at all reasonable to make Fn/FnShare/... special in this regard?)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eddyb I don't understand what you mean here. How is one opaque and the other not?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did say that byref/byvalue is not the best wording for this.
The vtable optimization for one method is specific to &Trait and &mut Trait, it's not an universal optimization by any means.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Thu, Jun 12, 2014 at 02:03:31AM -0700, Huon Wilson wrote:

Does #48 cover [exclusive traits] at all?

Not in and of itself, though it does lay the groundwork. I think the
way to express exclusive traits would be to add a negative trait
bound, so that you could do something like

trait Even { }
trait Odd : !Even { }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Wed, Jun 11, 2014 at 08:57:28PM -0700, Eric Reed wrote:

If we don't have a subtyping relationship on the closure traits,
then we'll just end up with a needless variants of higher-order
functions.

I agree this situation is to be avoided. Subtrait relationships are
not necessarily the only way to do that, however (though they may be a
good way).

My biggest concern is ergonomic and points at a general weak point in
our trait system as compared to a traditional OO setup. Basically,
although we use OO terminology like "supertrait", we don't in fact
offer an OO system where subtraits can override supertrait methods and
so on. This basically means that people who manually implement the
close traits will have to implement more than one trait in many
(most?) cases.

In other words, if we had a closure hierarchy like:

trait FnOnce<A,R> {
    fn call_once(self, arg: A) -> R;
}

trait Fn<A,R> : FnOnce<A, R> {
    fn call(&mut self, arg: A) -> R;
}

trait FnShare<A,R> : Fn<A, R> {
    fn call_share(&self, arg: A) -> R;
}

And now I have some type that implements FnShare:

struct Foo;

impl FnShare<(),()> for Foo {
    fn call_share(&self, arg: ()) -> () { ... }
}

This will yield an error because there is no impl FnOnce
nor Fn. I'd have to write those manually:

impl Fn<(),()> for Foo {
    fn call(&mut self, arg: ()) -> () {
        self.call_share(arg)
    }
}

impl FnOnce<(),()> for Foo {
    fn call_once(&mut self, arg: ()) -> () {
        self.call_share(arg)
    }
}

Maybe this doesn't matter, since it will be uncommon to implement
these traits manually, and perhaps we will at some point grow the
ability for a "subtrait" to specify default impls for
supertraits. This would take some careful design to get right though.

If we did instead have an impl like:

impl<A,R,T:FnShare<A,R>> Fn<A,R> for T { ... }

then there'd be no such issue. Still, that'd require some other
extensions to permit.

fn call(&mut self, args: A) -> R;
}

trait FnShare<A,R> {
fn call(&self, args: A) -> R;
}

trait FnOnce<A,R> {
fn call(&self, args: A) -> R;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is the FnOnce trait, its method should be fn call(self, args: A) -> R, right? (That is, self, not &self.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is self, which I believe it should be, it will be impossible to use FnOnce boxed, because trait objects cannot use self-by-value.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no inherent reason we can't make by-value self work with Box<Trait>. It just needs to generate a wrapper function to use in the vtable. An extra direct call after the indirect call would have a bit of overhead, but since a dynamic memory allocation is always paired with each call for non-zero size self, it doesn't seem important.

}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if someone makes an impl of one of the Fn traits where A is not a tuple? E.g. impl Fn<int, int> for Foo?

Essentially, `a(b, c, d)` becomes sugar for one of the following:

Fn::call(&mut a, (b, c, d))
FnShare::call(&a, (b, c, d))
FnOnce::call(a, (b, c, d))

To integrate with this, closure expressions are then translated into a
fresh struct that implements one of those three traits. The precise
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to include an explicit example of this translation/desugaring as part of the RFC presentation, just so a reader can see concretely how the closure expression turns into a struct construction.

In particular, someone might be wondering if the translation of foo({ let x: i32 = 3; |&: y:i32| -> i32 x + y }) is

struct Fresh { x: i32 }
impl FnShare<(i32,), i32> for Fresh { fn call_share(&self, y: i32) -> i32 { x + y }
foo(&Fresh { x: 3 } as &FnShare)

or if it is

struct Fresh { x: i32 }
impl FnShare<(i32,), i32> for Fresh { fn call_share(&self, y: i32) -> i32 { x + y }
foo(Fresh { x: 3 })

(I have deliberately left out the signature of foo, since my understanding is that the desugaring should not be affected by the type of foo.)

trait is currently indicated using explicit syntax but may eventually
be inferred.

This change gives user control over virtual vs static dispatch. This
works in the same way as generic types today:

fn foo(x: &mut Fn<int,int>) -> int {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and the next couple of example functions) should be Fn<(int,), int> rather than Fn<int, int>, I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Wed, Jun 11, 2014 at 03:37:11PM -0700, Chris Morgan wrote:

This (and the next couple of example functions) should be Fn<(int,), int> rather than Fn<int, int>, I think?

Sadly yes. Thanks.

x(2) // virtual dispatch
}

fn foo<F:Fn<int,int>>(x: &mut F) -> int {
x(2) // static dispatch
}

The change also permits returning closures, which is not currently
possible (the example relies on the proposed `impl` syntax from
rust-lang/rfcs#105):

fn foo(x: impl Fn<int,int>) -> impl Fn<int,int> {
|v| x(v * 2)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, with the type sugar proposed below this would look like fn foo(x: impl |int|->int) -> impl |int|->int {, correct?


Basically, in this design there is nothing special about a closure.
Closure expressions are simply a convenient way to generate a struct
that implements a suitable `Fn` trait.

## Bind by reference vs bind by value

When creating a closure, it is now possible to specify whether the
closure should capture variables from its environment ("upvars") by
reference or by value. The distinction is indicated using the leading
keyword `ref`:

|| foo(a, b) // captures `a` and `b` by value

ref || foo(a, b) // captures `a` and `b` by reference, as today
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this "desugar" to something like let a_ref = &a; let b_ref = &b; || foo(*a_ref, *b_ref)? (Meaning there's no special handling, other than the nicer captures.)

Edit: nevermind, "detailed design" covers this and more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Wed, Jun 11, 2014 at 04:49:41AM -0700, Huon Wilson wrote:

Would this "desugar" to something like let a_ref = &a; let b_ref = &b; || foo(*a_ref, *b_ref)? (Meaning there's no special handling,
other than the nicer captures.)

Essentially, except that:

  1. We infer whether we want & vs &mut.
  2. We internally support &uniq, as today.


### Reasons to bind by value

Bind by value is useful when creating closures that will escape from
the stack frame that created them, such as task bodies (`spawn(||
...)`) or combinators. It is also useful for moving values out of a
closure, though it should be possible to enable that with bind by
reference as well in the future.

### Reasons to bind by reference

Bind by reference is useful for any case where the closure is known
not to escape the creating stack frame. This frequently occurs
when using closures to encapsulate common control-flow patterns:

map.insert_or_update_with(key, value, || ...)
opt_val.unwrap_or_else(|| ...)

In such cases, the closure frequently wishes to read or modify local
variables on the enclosing stack frame. Generally speaking, then, such
closures should capture variables by-reference -- that is, they should
store a reference to the variable in the creating stack frame, rather
than copying the value out. Using a reference allows the closure to
mutate the variables in place and also avoids moving values that are
simply read temporarily.

The vast majority of closures in use today are should be "by
reference" closures. The only exceptions are those closures that wish
to "move out" from an upvar (where we commonly use the so-called
"option dance" today). In fact, even those closures could be "by
reference" closures, but we will have to extend the inference to
selectively identify those variables that must be moved and take those
"by value".

# Detailed design

## Closure expression syntax

Closure expressions will have the following form (using EBNF notation,
where `[]` denotes optional things and `{}` denotes a comma-separated
list):

CLOSURE = ['ref'] '|' [SELF] {ARG} '|' ['->' TYPE] EXPR
SELF = ':' | '&' ':' | '&' 'mut' ':'
ARG = ID [ ':' TYPE ]

The optional keyword `ref` is used to indicate whether this closure
captures *by reference* or *by value*.

Closures are always translated into a fresh struct type with one field
per upvar. In a by-value closure, the types of these fields will be
the same as the types of the corresponding upvars (modulo `&mut`
reborrows, see below). In a by-reference closure, the types of these
fields will be a suitable reference (`&`, `&mut`, etc) to the
variables being borrowed.

### By-value closures

The default form for a closure is by-value. This implies that all
upvars which are referenced are copied/moved into the closure as
appropriate. There is one special case: if the type of the value to be
moved is `&mut`, we will "reborrow" the value when it is copied into
the closure. That is, given an upvar `x` of type `&'a mut T`, the
value which is actually captured will have type `&'b mut T` where `'b
<= 'a`. This rule is consistent with our general treatment of `&mut`,
which is to aggressively reborrow wherever possible; moreover, this
rule cannot introduce additional compilation errors, it can only make
more programs successfully typecheck.

### By-reference closures

A *by-reference* closure is a convenience form in which values used in
the closure are converted into references before being captured. By
reference closures are always rewritable into by value closures if
desired, but the rewrite can often be cumbersome and annoying.

Here is a (rather artificial) example of a by-reference closure in
use:

let in_vec: Vec<int> = ...;
let mut out_vec: Vec<int> = Vec::new();
let opt_int: Option<int> = ...;

opt_int.map(ref |v| {
out_vec.push(v);
in_vec.fold(v, |a, &b| a + b)
});

This could be rewritten into a by-value closure as follows:

let in_vec: Vec<int> = ...;
let mut out_vec: Vec<int> = Vec::new();
let opt_int: Option<int> = ...;

opt_int.map({
let in_vec = &in_vec;
let out_vec = &mut in_vec;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo (out_vec)

|v| {
out_vec.push(v);
in_vec.fold(v, |a, &b| a + b)
}
})

In this case, the capture closed over two variables, `in_vec` and
`out_vec`. As you can see, the compiler automatically infers, for each
variable, how it should be borrowed and inserts the appropriate
capture.

In the body of a `ref` closure, the upvars continue to have the same
type as they did in the outer environment. For example, the type of a
reference to `in_vec` in the above example is always `Vec<int>`,
whether or not it appears as part of a `ref` closure. This is not only
convenient, it is required to make it possible to infer whether each
variable is borrowed as an `&T` or `&mut T` borrow.

Note that there are some cases where the compiler internally employs a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would &uniq still be used under this plan for closures?

form of borrow that is not available in the core language,
`&uniq`. This borrow does not permit aliasing (like `&mut`) but does
not require mutability (like `&`). This is required to allow
transparent closing over of `&mut` pointers as
[described in this blog post][p].

**Evolutionary note:** It is possible to evolve by-reference
closures in the future in a backwards compatible way. The goal would
be to cause more programs to type-check by default. Two possible
extensions follow:

- Detect when values are *moved* and hence should be taken by value
rather than by reference. (This is only applicable to once
closures.)
- Detect when it is only necessary to borrow a sub-path. Imagine a
closure like `ref || use(&context.variable_map)`. Currently, this
closure will borrow `context`, even though it only *uses* the field
`variable_map`. As a result, it is sometimes necessary to rewrite
the closure to have the form `{let v = &context.variable_map; ||
use(v)}`. In the future, however, we could extend the inference so
that rather than borrowing `context` to create the closure, we would
borrow `context.variable_map` directly.

## Closure sugar in trait references

The current type for closures, `|T1, T2| -> R`, will be repurposed as
syntactic sugar for a reference to the appropriate `Fn` trait. This
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are using the word "reference" here in its English, as opposed to Rustic, sense?

shorthand be used any place that a trait reference is appropriate. The
full type will be written as one of the following:

<'a...'z> |T1...Tn|: K -> R
<'a...'z> |&mut: T1...Tn|: K -> R
<'a...'z> |&: T1...Tn|: K -> R
<'a...'z> |: T1...Tn|: K -> R

Each of which would then be translated into the following trait
references, respectively:

<'a...'z> Fn<(T1...Tn), R> + K
<'a...'z> Fn<(T1...Tn), R> + K
<'a...'z> FnShare<(T1...Tn), R> + K
<'a...'z> FnOnce<(T1...Tn), R> + K
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of the above trait references has neither a leading & nor a leading impl (i.e. the syntax from #105).

Does that mean that someone using the |T1..Tn|: K -> R syntax (i.e. our current syntax and soon-to-be "sugar") would need to start using a leading & or impl ? Or is this relying in some way on passing an unsized type by value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Mon, Jun 16, 2014 at 01:23:42PM -0700, Felix S Klock II wrote:

Each of the above trait references has neither a leading & nor a leading impl (i.e. the syntax from #105).

Right, because they are trait references.

Does that mean that someone using the |T1..Tn|: K -> R syntax
(i.e. our current syntax and soon-to-be "sugar") would need to start
using a leading & or impl ?

Yes.

Or is this relying in some way on passing an unsized type by value?

Originally I wanted this, but we've dropped the notion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I think the RFC should be updated to make that more apparent: e.g. show some code from today and show how it needs to be rewritten to fit with the changes from this RFC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I was quite torn when I realized this consequence; it makes the closure sugar feel a little less sugar-y. I have been wondering if we should consider saying that the closure sugar expands into the corresponding Fn variant, but with a leading impl from #105 as well.

So with that suggestion in place, this sugared code under the new regime:

fn doubled(f: |int| -> int) -> |int| -> int { |x| { f(f(x)) } }

would be desugared into:

fn doubled(f: impl Fn<(int,), int>) -> impl Fn<(int,),int> { |x| { f(f(x)) } }
struct Fresh<F:Fn<(int,), int>> { f: F }
impl Fn<(int,), int> for Fresh { fn call(&mut self, (x,) : (int,)) -> int { self.f(self.f(x)) }

rather than requiring the user to write
fn doubled(f: impl |int| -> int) -> impl |int| -> int { |x| { f(f(x)) } }

To get today's semantics of dynamically-dispatched closure types, under my suggested changes you would be forced to write &Fn<(T1,..,Tk), R> (or perhaps just &Fn<(T1,...,Tk)> -> R if that side-proposal goes through).


I do not know, it is hard for me to gauge how painful each of these options are. I guess I just mention this to point out that the sugar, as proposed, is not adding that much. But on the other hand, the change I am proposing would force a lot of people's current code to change (that, or force it to start behaving quite a bit differently with respect to generated code size and what not; and also that object methods would not be able to take |T1..Tn| -> T arguments since they cannot support impl Trait arguments -- maybe that is the major point that kills this suggestion).

And also, I will admit that while I say above that the proposed sugar is not adding "that much", there is another point-of-view of the proposed sugar that in fact it adds "just enough".


Note that the bound lifetimes `'a...'z` are not in scope for the bound
`K`.

# Drawbacks

This model is more complex than the existing model in some respects
(but the existing model does not serve the full set of desired use cases).

# Alternatives

There is one aspect of the design that is still under active
discussion:

**Introduce a more generic sugar.** It was proposed that we could
introduce `Trait(A, B) -> C` as syntactic sugar for `Trait<(A,B),C>`
rather than retaining the form `|A,B| -> C`. This is appealing but
removes the correspondence between the expression form and the
corresponding type. One (somewhat open) question is whether there will
be additional traits that mirror fn types that might benefit from this
more general sugar.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not just Trait(A, B) -> C, but any Type(A, B) -> C. The reason I prefer this approach is that there are too many degrees of freedom in the closure types to be able to conveniently capture enough of them with specific built-in sugar. There's the trait itself and the containing type in the case of boxed closures and additional (built-in) trait bounds, and possibly lifetimes as well.

If |:&mut T1...Tn| -> R is sugar for a trait, then if you wish to pass a boxed mutable closure (incidentally the only type of closure currently in the language besides proc), do you have to write &mut |:&mut int| -> int? That's somewhat unsightly. If we make the sugar refer to a boxed closure instead to address this case, then we wouldn't have sugar for unboxed closures. We could try to make a typedef for it: type MutFn<'s, A, R> = &'s mut |:&mut A| -> R, but then we no longer have the sugar.

With the generic sugar, we can write:

trait FnMut(A) -> R {
    ...
}

and we can use the same sugar to make typedefs as well, for instance our earlier boxed mutable closure:

type MutFn(A) -> R = &mut FnMut(A) -> R;

There are aspects of this idea that still need to be given thought! For instance, I don't know where to put the 's lifetime in the above typedef.

We can also recover the current proc type, if we feel like it:

type Proc(A) -> R = Box<FnOnce(A) -> R + Send>;

And in general, users can use the sugar to make sweet-looking typedefs for whichever sorts of closures are employed frequently in the given API, relieving the language itself of the responsibility to try to anticipate all of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. I too prefer this syntax. I hadn't thought about the benefits of extending it more broadly to type aliases.


**Tweak trait names.** In conjunction with the above, there is some
concern that the type name `fn(A) -> B` for a bare function with no
environment is too similar to `Fn(A) -> B` for a closure. To remedy
that, we could change the name of the trait to something like
`Closure(A) -> B` (naturally the other traits would be renamed to
match).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the Fn names, but if we do want to change them I would go with Call.


Then there are a large number of permutations and options that were
largely rejected:

**Only offer by-value closures.** We tried this and found it
required a lot of painful rewrites of perfectly reasonable code.

**Make by-reference closures the default.** We felt this was
inconsistent with the language as a whole, which tends to make "by
value" the default (e.g., `x` vs `ref x` in patterns, `x` vs `&x` in
expressions, etc.).

**Use a capture clause syntax that borrows individual variables.** "By
value" closures combined with `let` statements already serve this
role. Simply specifying "by-reference closure" also gives us room to
continue improving inference in the future in a backwards compatible
way. Moreover, the syntactic space around closures expressions is
extremely constrained and we were unable to find a satisfactory
syntax, particularly when combined with self-type annotations.
Finally, if we decide we *do* want the ability to have "mostly
by-value" closures, we can easily extend the current syntax by writing
something like `(ref x, ref mut y) || ...` etc.

**Retain the proc expression form.** It was proposed that we could
retain the `proc` expression form to specify a by-value closure and
have `||` expressions be by-reference. Frankly, the main objection to
this is that nobody likes the `proc` keyword.

**Use variadic generics in place of tuple arguments.** While variadic
generics are an interesting addition in their own right, we'd prefer
not to introduce a dependency between closures and variadic
generics. Having all arguments be placed into a tuple is also a
simpler model overall. Moreover, native ABIs on platforms of interest
treat a structure passed by value identically to distinct
arguments. Finally, given that trait calls have the "Rust" ABI, which
is not specified, we can always tweak the rules if necessary (though
their advantages for tooling when the Rust ABI closely matches the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo? their -> there are

native ABI).

**Use inference to determine the self type of a closure rather than an
annotation.** We retain this option for future expansion, but it is
not clear whether we can always infer the self type of a
closure. Moreover, using inference rather a default raises the
question of what to do for a type like `|int| -> uint`, where
inference is not possible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this talking about a closure body? How is |int| -> uint even relevant?
I feel that the idea presented in this #97 comment (excluding trait inheritance) has been ignored/rejected too hastily.
If "inference" here refers to determining only one trait a closure body represents, then it's not even the same thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also like clarification on this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant by |int| -> uint is that we cannot infer within a type context and would therefore have to require explicit annotation there (or else defaults).


**Default to something other than `&mut self`.** It is our belief that
this is the most common use case for closures.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reasoning here is the opposite of the reasoning from the "Make by-reference closures the default." point from above:

We felt this was inconsistent with the language as a whole, which tends to make "by value" the default

In both cases, there's tension between the assumed common case and the defaults elsewhere in the language. The most common case is assumed to be by-reference mutating closures. But everywhere else the language defaults to by-value and immutable. The RFC is siding with consistency in one case (by-value) but convenience in the other case (mutable). I'm not saying this is necessarily wrong, but I do think it deserves an explicit defense at least.

If we have the generic type-level closure sugar and also infer the implemented trait(s) from the bodies of closure literals, then going for consistency on both counts is not too painful, because the explicit (&mut:) annotation can be avoided on both the type- and value levels.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Sun, Jun 15, 2014 at 02:46:19PM -0700, Gábor Lehel wrote:

This reasoning is the opposite of the reasoning from the "Make by-reference closures the default." point from above:

Yes, I know.

In both cases, there's tension between the assumed common case and
the defaults elsewhere in the language. The most common case is
assumed to be by-reference mutating closures. But everywhere else
the language defaults to by-value and immutable. The RFC is siding
with consistency in one case (by-value) but convenience in the other
case (mutable). I'm not saying this is necessarily wrong, but I do
think it deserves an explicit defense at least.

Noted. I'll try to add something in the next version, or perhaps make
other changes, but for posterity: my feeling is that by reference vs
by value makes less difference, since our statistics (and experience)
suggests that for most captures the two are equivalent. So for most
closures people can use by-value or by-reference as they choose, and
we'll only need to write ref || ... for a small percentage.

In contrast, if we have to write |&mut: ...| as the common case,
that's clearly going to be painful. |&: is much shorter and will be
written less frequently.

If we have the generic type-level closure sugar and also infer the
implemented trait(s) from the bodies of closure literals, then going
for consistency on both counts is not too painful, because the
explicit (&mut:) annotation can be avoided on both the type- and
value levels.

Yes, inference would be nice, though we still must decide what to name
the types (what is Fn?).

In any case, I am basically convinced that inference should be
workable. Basically the idea would be to wait to decide the self type
until the end of the typeck phase (type inference). We can then
determine which (if any) traits the closure is required to support:
the self type makes no difference to typeck, only to latter passes.


# Transition plan

TBD. pcwalton is working furiously as we speak.

# Unresolved questions

## Closures that are quantified over lifetimes

A separate RFC is needed to describe bound lifetimes in trait
references. For example, today one can write a type like `<'a> |&'a A|
-> &'a B`, which indicates a closure that takes and returns a
reference with the same lifetime specified by the caller at each
call-site. Note that a trait reference like `Fn<(&'a A), &'a B>`,
while syntactically similar, does *not* have the same meaning because
it lacks the universal quantifier `<'a>`. Therefore, in the second
case, `'a` refers to some specific lifetime `'a`, rather than being a
lifetime parameter that is specified at each callsite. The high-level
summary of the change therefore is to permit trait references like
`<'a> Fn<(&'a A), &'a B>`; in this case, the value of `<'a>` will be
specified each time a method or other member of the trait is accessed.

[p]: http://smallcultfollowing.com/babysteps/blog/2014/05/13/focusing-on-ownership/