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

Conversation

Projects
None yet
@nikomatsakis
Contributor

nikomatsakis commented Jun 11, 2014

A relatively complete description of by reference closures that aims to encompass entirety of current thinking, except for bound lifetimes.

cc @pcwalton

@huonw

This comment has been minimized.

Show comment
Hide comment
@huonw

huonw Jun 11, 2014

Member

Could this have a more descriptive title?

Member

huonw commented Jun 11, 2014

Could this have a more descriptive title?

Therefore, an expression like `a(...)` will be desugar into an
invocation of one of the following traits:
trait Fn<A,R> {

This comment has been minimized.

@huonw

huonw Jun 11, 2014

Member

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

@huonw

huonw Jun 11, 2014

Member

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

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 11, 2014

Contributor

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.

@nikomatsakis

nikomatsakis Jun 11, 2014

Contributor

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.

This comment has been minimized.

@anasazi

anasazi Jun 12, 2014

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.

@anasazi

anasazi Jun 12, 2014

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.

This comment has been minimized.

@eddyb

eddyb Jun 12, 2014

Member

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.

@eddyb

eddyb Jun 12, 2014

Member

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.

This comment has been minimized.

@thestinger

thestinger Jun 12, 2014

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

@thestinger

thestinger Jun 12, 2014

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

This comment has been minimized.

@huonw

huonw Jun 12, 2014

Member

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?)

@huonw

huonw Jun 12, 2014

Member

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?)

This comment has been minimized.

@anasazi

anasazi Jun 12, 2014

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

@anasazi

anasazi Jun 12, 2014

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

This comment has been minimized.

@eddyb

eddyb Jun 12, 2014

Member

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.

@eddyb

eddyb Jun 12, 2014

Member

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.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 13, 2014

Contributor

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 { }
@nikomatsakis

nikomatsakis Jun 13, 2014

Contributor

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 { }

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 13, 2014

Contributor

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.

@nikomatsakis

nikomatsakis Jun 13, 2014

Contributor

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.

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

This comment has been minimized.

@huonw

huonw Jun 11, 2014

Member

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.

@huonw

huonw Jun 11, 2014

Member

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.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 12, 2014

Contributor

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.
@nikomatsakis

nikomatsakis Jun 12, 2014

Contributor

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.
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

This comment has been minimized.

@huonw

huonw Jun 11, 2014

Member

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

@huonw

huonw Jun 11, 2014

Member

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

@nikomatsakis nikomatsakis changed the title from Initial commit to Unboxed closures Jun 11, 2014

# Summary
- Convert function call `a(b, ..., z)` into an overloadable operator

This comment has been minimized.

@bstrie

bstrie Jun 11, 2014

Contributor

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

@bstrie

bstrie Jun 11, 2014

Contributor

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

Show outdated Hide outdated active/0000-closures.md
Show outdated Hide outdated active/0000-closures.md
@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 11, 2014

Contributor

It was pointed out that to support Box<FnOnce()> we need rust-lang/rust#10672

Contributor

nikomatsakis commented Jun 11, 2014

It was pointed out that to support Box<FnOnce()> we need rust-lang/rust#10672

@bharrisau

This comment has been minimized.

Show comment
Hide comment
@bharrisau

bharrisau Jun 11, 2014

Does the explicit 'ref' keyword indicate that by-ref is more expensive or in some way less preferred than by-value? Or are they much the same and 'ref' was just used because it already exists as a keyword?

bharrisau commented Jun 11, 2014

Does the explicit 'ref' keyword indicate that by-ref is more expensive or in some way less preferred than by-value? Or are they much the same and 'ref' was just used because it already exists as a keyword?

@thestinger

This comment has been minimized.

Show comment
Hide comment
@thestinger

thestinger Jun 11, 2014

By-value is preferred because it doesn't tie the closure to a stack frame, and most values are small so it's usually less expensive to capture by-value. In many cases, there are already references and it doesn't make sense to use two layers of indirection.

thestinger commented Jun 11, 2014

By-value is preferred because it doesn't tie the closure to a stack frame, and most values are small so it's usually less expensive to capture by-value. In many cases, there are already references and it doesn't make sense to use two layers of indirection.

@chris-morgan

This comment has been minimized.

Show comment
Hide comment
@chris-morgan

chris-morgan Jun 11, 2014

Member

I presume that this would not affect the behaviour of method lookup with .? Sometimes it’d be rather nice if I could easily have self.field(…) for a field that implements Fn, rather than (self.field)(…).

Member

chris-morgan commented Jun 11, 2014

I presume that this would not affect the behaviour of method lookup with .? Sometimes it’d be rather nice if I could easily have self.field(…) for a field that implements Fn, rather than (self.field)(…).

@chris-morgan

This comment has been minimized.

Show comment
Hide comment
@chris-morgan

chris-morgan Jun 11, 2014

Member

How about overloading? Would something like this, reminiscent of Qt’s overloaded getters and setters, be possible?

struct Foo {
    value: Bar,
}

impl<'a> FnShare<(), &'a Bar> for Foo {
    fn call(&'a self, _: ()) -> &'a Bar {  // uh oh, I guess 'a is problematic
        self.value
    }
}

impl Fn<(Bar,), ()> for Foo {
    fn call(&mut self, (value,): (Bar,)) {
        self.value = value

Ignoring the problem of the lifetime for a moment—would foo() and foo(Bar) both be expected to work? (We can declare both implementations to be of Fn or of FnShare if we like. It’s just the concept, whether duality of call signatures would be thus permitted).

Returning to the self lifetime, I can imagine being able to express that being a corner-case desirable.

Member

chris-morgan commented Jun 11, 2014

How about overloading? Would something like this, reminiscent of Qt’s overloaded getters and setters, be possible?

struct Foo {
    value: Bar,
}

impl<'a> FnShare<(), &'a Bar> for Foo {
    fn call(&'a self, _: ()) -> &'a Bar {  // uh oh, I guess 'a is problematic
        self.value
    }
}

impl Fn<(Bar,), ()> for Foo {
    fn call(&mut self, (value,): (Bar,)) {
        self.value = value

Ignoring the problem of the lifetime for a moment—would foo() and foo(Bar) both be expected to work? (We can declare both implementations to be of Fn or of FnShare if we like. It’s just the concept, whether duality of call signatures would be thus permitted).

Returning to the self lifetime, I can imagine being able to express that being a corner-case desirable.

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`

This comment has been minimized.

@anasazi

anasazi Jun 12, 2014

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).

@anasazi

anasazi Jun 12, 2014

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).

This comment has been minimized.

@eddyb

eddyb Jun 12, 2014

Member

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.

@eddyb

eddyb Jun 12, 2014

Member

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.

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.

This comment has been minimized.

@eddyb

eddyb Jun 12, 2014

Member

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.

@eddyb

eddyb Jun 12, 2014

Member

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.

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

I would also like clarification on this point.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

I would also like clarification on this point.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 17, 2014

Contributor

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).

@nikomatsakis

nikomatsakis Jun 17, 2014

Contributor

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).

@bstrie

This comment has been minimized.

Show comment
Hide comment
@bstrie

bstrie Jun 12, 2014

Contributor

I would like to see some discussion from the devs regarding @eddyb's proposal in the prior RFC (#97 (comment)). AFAICT it has only been ignored thus far.

Contributor

bstrie commented Jun 12, 2014

I would like to see some discussion from the devs regarding @eddyb's proposal in the prior RFC (#97 (comment)). AFAICT it has only been ignored thus far.

@pnkfelix pnkfelix referenced this pull request Jun 12, 2014

Closed

Closure reform #2202

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 13, 2014

Contributor

On Thu, Jun 12, 2014 at 03:07:38PM -0700, Ben Striegel wrote:

I would like to see some discussion from the devs regarding @eddyb's
proposal in the prior RFC
(#97 (comment)). AFAICT
it has only been ignored thus far.

@bstrie thanks for bringing that up. I had in fact missed it amongst
the other comments. My thoughts are as follows:

  1. In order to achieve inference and obviate the need for explicit
    declaration of self type, I had in fact intended to wait until
    vtable resolution, as @eddyb suggests.
  2. Having the compiler implement multiple traits for a closure is an
    interesting thought. The disadvantage compared to supertraits or
    impl-based-adapters is that if I write generic code like fn foo<T:Fn<...>>, I can't reuse T in a context where FnMut is
    required without having an extra bound (T:Fn+FnMut) or making
    explicit use of an adapter. (It's also more complicated in the
    compiler itself.)
  3. The advantage of course is that it retains the property of having
    exactly one method which may permit some optimization. I'd like to
    see some microbenchmarks on the importance of this.
Contributor

nikomatsakis commented Jun 13, 2014

On Thu, Jun 12, 2014 at 03:07:38PM -0700, Ben Striegel wrote:

I would like to see some discussion from the devs regarding @eddyb's
proposal in the prior RFC
(#97 (comment)). AFAICT
it has only been ignored thus far.

@bstrie thanks for bringing that up. I had in fact missed it amongst
the other comments. My thoughts are as follows:

  1. In order to achieve inference and obviate the need for explicit
    declaration of self type, I had in fact intended to wait until
    vtable resolution, as @eddyb suggests.
  2. Having the compiler implement multiple traits for a closure is an
    interesting thought. The disadvantage compared to supertraits or
    impl-based-adapters is that if I write generic code like fn foo<T:Fn<...>>, I can't reuse T in a context where FnMut is
    required without having an extra bound (T:Fn+FnMut) or making
    explicit use of an adapter. (It's also more complicated in the
    compiler itself.)
  3. The advantage of course is that it retains the property of having
    exactly one method which may permit some optimization. I'd like to
    see some microbenchmarks on the importance of this.
@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 13, 2014

Contributor

On Wed, Jun 11, 2014 at 03:08:15PM -0700, Ben Harris wrote:

Does the explicit 'ref' keyword indicate that by-ref is more
expensive or in some way less preferred than by-value? Or are they
much the same and 'ref' was just used because it already exists as a
keyword?

I personally do not believe ref intends a value judgement either
way. In fact, creating a ref closure is the same cost (today) as a
by-value closure, and could be optimized to be cheaper (by storing a
pointer to the stack frame itself, rather than individual variables).

Rather, we chose to use the ref keyword because it was lightweight
and consistent with match statements and other parts of the language:
basically, all operations that create a reference into the stack now
employ either & or ref keyword.

Contributor

nikomatsakis commented Jun 13, 2014

On Wed, Jun 11, 2014 at 03:08:15PM -0700, Ben Harris wrote:

Does the explicit 'ref' keyword indicate that by-ref is more
expensive or in some way less preferred than by-value? Or are they
much the same and 'ref' was just used because it already exists as a
keyword?

I personally do not believe ref intends a value judgement either
way. In fact, creating a ref closure is the same cost (today) as a
by-value closure, and could be optimized to be cheaper (by storing a
pointer to the stack frame itself, rather than individual variables).

Rather, we chose to use the ref keyword because it was lightweight
and consistent with match statements and other parts of the language:
basically, all operations that create a reference into the stack now
employ either & or ref keyword.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 13, 2014

Contributor

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

I presume that this would not affect the behaviour of method lookup
with .? Sometimes it’d be rather nice if I could easily have
self.field(…) for a field that implements Fn, rather than
(self.field)(…).

Correct, behavior of method lookup does not change.

Contributor

nikomatsakis commented Jun 13, 2014

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

I presume that this would not affect the behaviour of method lookup
with .? Sometimes it’d be rather nice if I could easily have
self.field(…) for a field that implements Fn, rather than
(self.field)(…).

Correct, behavior of method lookup does not change.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 13, 2014

Contributor

I added a commit describing @eddyb and @anasazi proposals for subtrait relationships. Let me know if summary is inaccurate.

Contributor

nikomatsakis commented Jun 13, 2014

I added a commit describing @eddyb and @anasazi proposals for subtrait relationships. Let me know if summary is inaccurate.

@anasazi

This comment has been minimized.

Show comment
Hide comment
@anasazi

anasazi Jun 14, 2014

@nikomatsakis Looks accurate to me. Thanks for putting it in.

anasazi commented Jun 14, 2014

@nikomatsakis Looks accurate to me. Thanks for putting it in.

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

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

typo (out_vec)

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

typo (out_vec)

## 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

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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

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.

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 17, 2014

Contributor

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

@nikomatsakis

nikomatsakis Jun 17, 2014

Contributor

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

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).

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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

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

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 16, 2014

Contributor

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.

@nikomatsakis

nikomatsakis Jun 16, 2014

Contributor

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.

Employing this relationship, however, would require that any manual
implement of `FnShare` or `Fn` must implement adapters for the other
two traits, since a subtrait cannot provide a specialized default of
supertrait methods (yet?). On the other hand, having no relationship

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

I don't think this is a problem we should be particularly concerned about, because presently you can't make manual implementations of closure traits at all and no one seems to miss it, and it's probably not going to be done very frequently anyways.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

I don't think this is a problem we should be particularly concerned about, because presently you can't make manual implementations of closure traits at all and no one seems to miss it, and it's probably not going to be done very frequently anyways.

This comment has been minimized.

@thestinger

thestinger Jun 15, 2014

no one seems to miss it

That's incorrect, it's an important missing feature. The closure syntax in this proposal would simply be sugar for the most common case of callable objects after functions without environments.

@thestinger

thestinger Jun 15, 2014

no one seems to miss it

That's incorrect, it's an important missing feature. The closure syntax in this proposal would simply be sugar for the most common case of callable objects after functions without environments.

where `T:FnShare`. This will yield coherence errors unless we extend
the language with a means to declare traits as mutually exclusive
(which might be valuable, but no such system has currently been
proposed nor agreed upon).

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

Are we assuming the coherence system from your RFC about traits, i.e. impl<T: Foo> Bar for T doesn't conflict with every other impl of Bar, only for those types which also impl Foo?

In that case wouldn't this work?

impl<T: FnShare> Fn for T

impl<T: Fn> FnOnce for T

If a type impls FnShare, the first impl will give it Fn, and because it now impls Fn, the second impl will also give it FnOnce. If a type only impls Fn then only the second impl will match, and everything works.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

Are we assuming the coherence system from your RFC about traits, i.e. impl<T: Foo> Bar for T doesn't conflict with every other impl of Bar, only for those types which also impl Foo?

In that case wouldn't this work?

impl<T: FnShare> Fn for T

impl<T: Fn> FnOnce for T

If a type impls FnShare, the first impl will give it Fn, and because it now impls Fn, the second impl will also give it FnOnce. If a type only impls Fn then only the second impl will match, and everything works.

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 17, 2014

Contributor

Um, yes, I think that would work. Not sure why I didn't think of that in the first place. :)

@nikomatsakis

nikomatsakis Jun 17, 2014

Contributor

Um, yes, I think that would work. Not sure why I didn't think of that in the first place. :)

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

This comment has been minimized.

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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?

@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

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?

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Jun 15, 2014

Contributor

One other question that I didn't see addressed in the text: if a function expects a boxed closure, would you have to explicitly take a reference to the (unboxed) closure at the call site? E.g. foo.do_for_each(&mut ref |x| { ... })? Or would there be some kind of auto-ref to avoid the &mut here?

Contributor

glaebhoerl commented Jun 15, 2014

One other question that I didn't see addressed in the text: if a function expects a boxed closure, would you have to explicitly take a reference to the (unboxed) closure at the call site? E.g. foo.do_for_each(&mut ref |x| { ... })? Or would there be some kind of auto-ref to avoid the &mut here?

@huonw

This comment has been minimized.

Show comment
Hide comment
@huonw

huonw Jun 16, 2014

Member

I wonder if it's worth having something similar to C++'s mutable, e.g. mut |...| ....; and then mutation of by-value captures would only be valid when mut is specified.

Member

huonw commented Jun 16, 2014

I wonder if it's worth having something similar to C++'s mutable, e.g. mut |...| ....; and then mutation of by-value captures would only be valid when mut is specified.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 16, 2014

Contributor

On Sun, Jun 15, 2014 at 03:04:42PM -0700, Gábor Lehel wrote:

One other question that I didn't see addressed in the text: if a
function expects a boxed closure, would you have to explicitly take
a reference to the (unboxed) closure at the call site?
E.g. foo.for_each(&mut ref |x| { ... })? Or would there be some
kind of auto-ref to avoid the &mut here?

At present there is no sugar for this. It's possible this would fit
into a more general auto-ref proposal.

Contributor

nikomatsakis commented Jun 16, 2014

On Sun, Jun 15, 2014 at 03:04:42PM -0700, Gábor Lehel wrote:

One other question that I didn't see addressed in the text: if a
function expects a boxed closure, would you have to explicitly take
a reference to the (unboxed) closure at the call site?
E.g. foo.for_each(&mut ref |x| { ... })? Or would there be some
kind of auto-ref to avoid the &mut here?

At present there is no sugar for this. It's possible this would fit
into a more general auto-ref proposal.

FnOnce::call_once(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

This comment has been minimized.

@pnkfelix

pnkfelix Jun 16, 2014

Member

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.)

@pnkfelix

pnkfelix Jun 16, 2014

Member

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.)

<'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

This comment has been minimized.

@pnkfelix

pnkfelix Jun 16, 2014

Member

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?

@pnkfelix

pnkfelix Jun 16, 2014

Member

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?

This comment has been minimized.

@nikomatsakis

nikomatsakis Jun 17, 2014

Contributor

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.

@nikomatsakis

nikomatsakis Jun 17, 2014

Contributor

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.

This comment has been minimized.

@pnkfelix

pnkfelix Jun 17, 2014

Member

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.

@pnkfelix

pnkfelix Jun 17, 2014

Member

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.

This comment has been minimized.

@pnkfelix

pnkfelix Jun 17, 2014

Member

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".

@pnkfelix

pnkfelix Jun 17, 2014

Member

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".

@brson

This comment has been minimized.

Show comment
Hide comment
@brson
Contributor

brson commented Jul 30, 2014

@brson

This comment has been minimized.

Show comment
Hide comment
@brson

brson Jul 30, 2014

Contributor

Unboxed closures are a complex topic that will probably evolve during the implementation. We're probably going to update the RFC as we go, and will probably also feature gate some of the corner cases that may not be fully-bakable yet.

Contributor

brson commented Jul 30, 2014

Unboxed closures are a complex topic that will probably evolve during the implementation. We're probably going to update the RFC as we go, and will probably also feature gate some of the corner cases that may not be fully-bakable yet.

@shepmaster

This comment has been minimized.

Show comment
Hide comment
@shepmaster

shepmaster Sep 1, 2014

Member

Accepted as RFC 43

Looks like it is actually RFC 44

Member

shepmaster commented Sep 1, 2014

Accepted as RFC 43

Looks like it is actually RFC 44

withoutboats pushed a commit to withoutboats/rfcs that referenced this pull request Jan 15, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment