Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upUnboxed closures #114
Conversation
pnkfelix
reviewed
Jun 11, 2014
| } | ||
|
|
||
| trait FnOnce<A,R> { | ||
| fn call(&self, args: A) -> R; |
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jun 11, 2014
Member
Since this is the FnOnce trait, its method should be fn call(self, args: A) -> R, right? (That is, self, not &self.)
This comment has been minimized.
This comment has been minimized.
cmr
Jun 11, 2014
Member
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.
This comment has been minimized.
This comment has been minimized.
thestinger
Jun 11, 2014
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.
This comment has been minimized.
This comment has been minimized.
|
Could this have a more descriptive title? |
huonw
reviewed
Jun 11, 2014
| 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.
This comment has been minimized.
huonw
Jun 11, 2014
Member
There was a good suggestion for how these traits can inherit: #97 (comment)
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jun 11, 2014
Author
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.
This comment has been minimized.
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
Fnbound on an otherwiseFnShareable closure because no one is bothering to write theFnSharevariants.FnOncewould probably still be used due to its utility so we'd have thefoo/foo_onceexplosion 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.
This comment has been minimized.
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
Fnbound on an otherwiseFnShareable 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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
nikomatsakis
Jun 13, 2014
Author
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.
This comment has been minimized.
nikomatsakis
Jun 13, 2014
Author
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.
huonw
reviewed
Jun 11, 2014
|
|
||
| || 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.
This comment has been minimized.
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.
This comment has been minimized.
nikomatsakis
Jun 12, 2014
Author
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:
- We infer whether we want
&vs&mut. - We internally support
&uniq, as today.
huonw
reviewed
Jun 11, 2014
| 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.
This comment has been minimized.
nikomatsakis
changed the title
Initial commit
Unboxed closures
Jun 11, 2014
bstrie
reviewed
Jun 11, 2014
|
|
||
| # Summary | ||
|
|
||
| - Convert function call `a(b, ..., z)` into an overloadable operator |
This comment has been minimized.
This comment has been minimized.
bstrie
Jun 11, 2014
Contributor
Would this have any impact on bare functions? Would they effectively implement FnShare?
bstrie
reviewed
Jun 11, 2014
|
|
||
| fn foo(x: impl Fn<int,int>) -> impl Fn<int,int> { | ||
| |v| x(v * 2) | ||
| } |
This comment has been minimized.
This comment has been minimized.
bstrie
Jun 11, 2014
Contributor
To clarify, with the type sugar proposed below this would look like fn foo(x: impl |int|->int) -> impl |int|->int {, correct?
cmr
reviewed
Jun 11, 2014
| 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 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
It was pointed out that to support |
This comment has been minimized.
This comment has been minimized.
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? |
This comment has been minimized.
This comment has been minimized.
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
reviewed
Jun 11, 2014
| 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 { |
This comment has been minimized.
This comment has been minimized.
chris-morgan
Jun 11, 2014
Member
This (and the next couple of example functions) should be Fn<(int,), int> rather than Fn<int, int>, I think?
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jun 13, 2014
Author
Contributor
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 thanFn<int, int>, I think?
Sadly yes. Thanks.
This comment has been minimized.
This comment has been minimized.
|
I presume that this would not affect the behaviour of method lookup with |
This comment has been minimized.
This comment has been minimized.
|
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 = valueIgnoring the problem of the lifetime for a moment—would Returning to the |
anasazi
reviewed
Jun 12, 2014
| 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.
This comment has been minimized.
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.
This comment has been minimized.
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
reviewed
Jun 12, 2014
| 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.
This comment has been minimized.
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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jun 17, 2014
Author
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).
This was referenced Jun 12, 2014
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
On Thu, Jun 12, 2014 at 03:07:38PM -0700, Ben Striegel wrote:
@bstrie thanks for bringing that up. I had in fact missed it amongst
|
This comment has been minimized.
This comment has been minimized.
|
On Wed, Jun 11, 2014 at 03:08:15PM -0700, Ben Harris wrote:
I personally do not believe Rather, we chose to use the |
This comment has been minimized.
This comment has been minimized.
|
On Wed, Jun 11, 2014 at 03:39:57PM -0700, Chris Morgan wrote:
Correct, behavior of method lookup does not change. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
anasazi
commented
Jun 14, 2014
|
@nikomatsakis Looks accurate to me. Thanks for putting it in. |
pnkfelix
referenced this pull request
Jun 15, 2014
Closed
librustc: Copy or move upvars by value [breaking-change]. #14620
glaebhoerl
reviewed
Jun 15, 2014
|
|
||
| opt_int.map({ | ||
| let in_vec = &in_vec; | ||
| let out_vec = &mut in_vec; |
This comment has been minimized.
This comment has been minimized.
glaebhoerl
reviewed
Jun 15, 2014
| ## 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.
This comment has been minimized.
glaebhoerl
Jun 15, 2014
Contributor
You are using the word "reference" here in its English, as opposed to Rustic, sense?
glaebhoerl
reviewed
Jun 15, 2014
| 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.
This comment has been minimized.
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.
This comment has been minimized.
nikomatsakis
Jun 17, 2014
Author
Contributor
Indeed. I too prefer this syntax. I hadn't thought about the benefits of extending it more broadly to type aliases.
glaebhoerl
reviewed
Jun 15, 2014
| 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.
This comment has been minimized.
glaebhoerl
Jun 15, 2014
Contributor
I prefer the Fn names, but if we do want to change them I would go with Call.
glaebhoerl
reviewed
Jun 15, 2014
| 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.
This comment has been minimized.
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.
This comment has been minimized.
nikomatsakis
Jun 16, 2014
Author
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.
glaebhoerl
reviewed
Jun 15, 2014
| 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.
This comment has been minimized.
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.
This comment has been minimized.
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.
glaebhoerl
reviewed
Jun 15, 2014
| 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.
This comment has been minimized.
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.
This comment has been minimized.
nikomatsakis
Jun 17, 2014
Author
Contributor
Um, yes, I think that would work. Not sure why I didn't think of that in the first place. :)
glaebhoerl
reviewed
Jun 15, 2014
| trait FnOnce<A,R> { | ||
| fn call_once(&self, args: A) -> R; | ||
| } | ||
|
|
This comment has been minimized.
This comment has been minimized.
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?
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
I wonder if it's worth having something similar to C++'s |
This comment has been minimized.
This comment has been minimized.
|
On Sun, Jun 15, 2014 at 03:04:42PM -0700, Gábor Lehel wrote:
At present there is no sugar for this. It's possible this would fit |
pnkfelix
reviewed
Jun 16, 2014
| 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.
This comment has been minimized.
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
reviewed
Jun 16, 2014
| <'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.
This comment has been minimized.
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.
This comment has been minimized.
nikomatsakis
Jun 17, 2014
Author
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 leadingimpl(i.e. the syntax from #105).
Right, because they are trait references.
Does that mean that someone using the
|T1..Tn|: K -> Rsyntax
(i.e. our current syntax and soon-to-be "sugar") would need to start
using a leading&orimpl?
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.
This comment has been minimized.
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.
This comment has been minimized.
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
referenced this pull request
Jun 17, 2014
Closed
RFC: Add unboxed, abstract return types #105
pcwalton
referenced this pull request
Jul 3, 2014
Merged
RFC: Capture upvars by value unless the `ref` keyword is used #151
brson
merged commit c3cf523
into
rust-lang:master
Jul 30, 2014
This comment has been minimized.
This comment has been minimized.
|
Discussed at https://github.com/rust-lang/meeting-minutes/blob/master/weekly-meetings/2014-07-29.md. Accepted as RFC 43. Tracking: rust-lang/rust#14449 |
brson
referenced this pull request
Jul 30, 2014
Closed
Implement syntax for anonymous unboxed closures #14449
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
Looks like it is actually RFC 44 |
nikomatsakis commentedJun 11, 2014
A relatively complete description of by reference closures that aims to encompass entirety of current thinking, except for bound lifetimes.
cc @pcwalton