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 upAllow closure expressions to expand to a `&` or `&mut` temporary #756
Conversation
This comment has been minimized.
This comment has been minimized.
|
I think it's not common enough to deserve a special case. If this RFC is accepted, then nothing will stop people from asking for more special cases. Why, for example, generic integer literals can't be a bit more generic and turn into references to integral types when required? Ultimately, I'd support a general auto-ref coercion |
nrc
assigned
nikomatsakis
Jan 29, 2015
This comment has been minimized.
This comment has been minimized.
sinistersnare
commented
Jan 29, 2015
|
Is there reading somewhere that explains why creating a trait object is recommended over creating a generic and statically dispatched parameter? Looked at the motivations to find these, do you mind expanding?
|
This comment has been minimized.
This comment has been minimized.
|
This question was raised by @japaric on rust-lang/rust#21699:
The answer here is already defined by the coercion semantics. In this case, you'd be coercing to the type fn foo<F:FnMut>(x: &mut F)and the caller wrote |
This comment has been minimized.
This comment has been minimized.
|
@sinistersnare I wouldn't go so far as to say that one is recommended over the other, but there are definitely specific scenarios where a trait object is preferred:
|
This comment has been minimized.
This comment has been minimized.
|
@petrochenkov I guess that's the question. It seems worthwhile to me to add sugar for specific cases. I am rather negative on a generic autoref expansion. I personally prefer to see where moves and borrows occur; I found generic autoref more confusing than helpful when we used to have it (well, we had a more limited form, auto-cross-borrow). However, in this case the closure expression is an rvalue, so this information is not particularly helpful or interesting. (That said, the only kind of autoref that I am strictly opposed to is automatic |
This comment has been minimized.
This comment has been minimized.
And at the same time most of them occur invisibly in method calls and operators, including the
Hmm, auto-ref for all rvalues.. Looks like a nice idea actually. |
This comment has been minimized.
This comment has been minimized.
tikue
commented
Feb 3, 2015
|
I agree with @petrochenkov. If it's decided that it's a good idea to add auto-ref for closure rvalues, it'd probably be just as good to have auto-ref for all rvalues. |
This comment has been minimized.
This comment has been minimized.
|
On Tue, Feb 03, 2015 at 02:42:31PM -0800, Tim wrote:
I see this point of view, though I consider closure expressions |
This comment has been minimized.
This comment has been minimized.
|
I worry about the pedagogical consequences of having a distinction between lvalues and rvalues for the purposes of auto-ref. In general, the lvalue/rvalue distinction is a distinction that compiler writers care about, not one that ordinary programmers care about. (Yes, you have to know the difference on some level, but for most people the distinction is purely intuitive and not something that people have to consciously think about; it exists primarily so that the compiler will reject nonsensical assignments like |
This comment has been minimized.
This comment has been minimized.
|
I actually think its not that difficult to explain the difference between rvalues and lvalues in Rust, and it might even help pedagogically because they explain exactly when a move/copy happens, and when a temporary is created.
|
This comment has been minimized.
This comment has been minimized.
|
@pcwalton to be clear, though, autoref for all rvalues is not what is described in this RFC, which is rather more limited. As I wrote...somewhere, I don't really consider this a form of general autoref, but rather making the |
This comment has been minimized.
This comment has been minimized.
|
To rephrase, this proposal basically changes the explanation of
to:
|
This comment has been minimized.
This comment has been minimized.
Hmm, auto-ref for all literals, nice idea too :D |
This comment has been minimized.
This comment has been minimized.
|
My feeling is that, without a change like this (or the DST-by-value change mentioned in Alternatives), there will be a large ergonomic tax for using boxed closures. I believe that unboxed closures are already overused, which leads to significant code bloat and longer compilation times, and think we should try to adjust this balance. While I understand the consistency point some have raised, note that this is by no means a general coercion -- it is a relatively small modification to the Put differently, I think the argument shouldn't be about consistency so much as: does this negatively impact your ability to reason about real code? I am unable to come up with any examples where it does. As such, I'm in favor of this RFC as-is. |
aturon
added
the
T-lang
label
Jun 1, 2015
This comment has been minimized.
This comment has been minimized.
|
Since 1.0 has already been released without this (and 1.1 and quite possibly 1.2 as well), I don't think the ergonomic gains are worth the extra complexity and inconsistencies are worth carrying around for ever more. Everywhere else a value is borrowed (even for temporary literals), it has to be explicit. I don't find having to add |
This comment has been minimized.
This comment has been minimized.
|
Hear ye, hear ye. This RFC was moved into Final Comment Period as of Wed, June 10th. (Sorry, forgot to add this notice!) UPDATE: Fixed date :) |
nikomatsakis
added
the
final-comment-period
label
Jun 11, 2015
This comment has been minimized.
This comment has been minimized.
sinistersnare
commented
Jun 11, 2015
Today is Thursday June 11. |
This comment has been minimized.
This comment has been minimized.
DST by value would be great to have, but it seems somewhat orthogonal to this RFC. In particular, recursive functions that take closure arguments are still a pain to manage, even if no trait objects are involved at all. Consider a pattern like this (common enough in the compiler, at least): fn recursive<F:FnMut>(..., f: F) {
match ... {
Something(left, right) => { recursive(left, &mut f); recursive(right, &mut f); }
...
}
}This code will fail during monomorphization due to infinite expansion. The reason is that it gets called first with the original closure type (let's call it C). Then called with fn recursive<F:FnMut>(..., f: &mut F) {
match ... {
Something(left, right) => { recursive(left, f); recursive(right, f); }
...
}
}But now when I call fn recursive<F:FnMut>(..., f: F) {
recursive1(..., &mut f);
}
fn recursive1<F:FnMut>(..., f: &mut F) {
// as above, but call `recursive1`, not `recursive`
} |
This comment has been minimized.
This comment has been minimized.
This doesn't seem like a problem, to me. Edit: to explain a bit more, the first alternative seems very consistent with the rest rest of the language. Borrowing of function arguments is always explicit. If a function expects a The second alternative is a very common pattern for recursive algorithms: one often needs to perform some initial processing on a function's arguments before calling the actual recursive function, and I, personally, find myself doing this quite often in any language. Rust adds even more reasons to desire a wrapper, such as using |
This comment has been minimized.
This comment has been minimized.
|
I feel that @aturon summed up my opinion quite well on this RFC. I've been trying to somewhat aggressively use My opinion on closures is that they're fundamentally designed to be ergonomic. All closures we have today can be replaced with an I would personally feel that the drawbacks to this RFC are far outweighed by putting boxed closures on equal footing with unboxed closures, so I'm in favor. |
This comment has been minimized.
This comment has been minimized.
What makes this an annoyance? Specifically, what makes it more of an annoyance than everywhere else in the language where explicit borrows are required for literals and variables alike? In my mind, closures are (and should continue to be) a sugary literal for an anonymous struct created by the compiler. This means that borrowing should be treated exactly the same as for struct literals (requiring Ideally, I'd like to see passing a closure directly to a function and assigning it to a variable first work exactly the same in all cases. I realize this isn't 100% true, today, but at the very least we shouldn't be moving further from that goal. It seems like there are two main motivations wanting to take an Since adding this proposed sugar makes closures different from every other literal in the language, I don't think calling it an "ergonomic speed bump" is sufficient motivation. At the very least the RFC needs to explain why saying I also want to note that until we get by value DSTs, it is possible even today to avoid the code bloat of many instantiations without requiring the calling function to add @nikomatsakis As an aside, is there any reason let closure = |x| x*2;
do_something(&mut closure)can't me made to work with inference? It seems analogous to inferring the type of an integer literal or a vector based on its later usage. |
This comment has been minimized.
This comment has been minimized.
tikue
commented
Jun 17, 2015
|
Eh, I think there's a clear difference between array literals and closures. Array literal syntax exists because they're a primitive type; without the syntax you can't make an array. Closure syntax only exists to make it easy to use the Fn* traits. I don't think its unreasonable for them to have special handling as described in this RFC. That being said, I also dont think it's unreasonable to hold off on this RFC for a while. It's a nice-to-have, and it may be that by-value DSTs eliminate a majority of the cases this would be useful. If there's no urgency, why not wait and see where the chips fall. |
This comment has been minimized.
This comment has been minimized.
The key difference here is that using a generic vs using a trait object has many compile-time implications, and using trait objects is currently hamstrung ergonomically by requiring Closures are already a special case for "expand into something magical" and as @aturon mentioned earlier I don't think that this is really adding any more magic than is already present. |
This comment has been minimized.
This comment has been minimized.
Again, how is this different from non-closure traits? Functions still have to decide whether to be generic or take a trait object, and if they choose the latter, anyone passing a variable or literal has to prepend
I very much disagree. One of the things I like about Rust closures is they are conceptually quite understandable and not particularly magical: each closure becomes an instance of an unnameable, compiler-defined struct type that implements one or more traits corresponding to the function call operator. You can even create your own structs that do the same thing (though I realize the exact syntax hasn't been stabilized, yet). All of the magic has to do with creating the struct: what data fields it has, whether they are values or references, which of the function traits it implements, etc. As I recall, most if not all of that is determined by the function body, what variables it uses, and how it uses them. Aside from the creation of the anonymous struct type, they currently behave just like struct literals, and I think that is important. Losing this for some limited ergonomic gains that will be made redundant by planned future features seems ridiculous. Especially since it is possible to achieve the same result (callers don't have to use |
This comment has been minimized.
This comment has been minimized.
|
I'm of two minds here. I hear and respect @rkjnsn's concern that having I guess the question is whether closures are different from other things, like arrays. I think that they are. It is great to understand that a closure can be desugared into a struct that implements a trait, but in terms of how they are used closures do not feel so very much like "data structures" but rather "control flow". This is why, for example, it makes sense to have closures default to by-ref and require the |
This comment has been minimized.
This comment has been minimized.
|
Hm... I think after consideration I'm in favor of this RFC, even though I'm generally not in favor of such sugar. Because you can already replicate this behavior by dispatching inside a thin wrapper (as @rkjnsn pointed out), and it wouldn't apply to closures assigned to a variable, there isn't a situation where this would actually make your code harder to reason about, IMO. |
This comment has been minimized.
This comment has been minimized.
theemathas
commented
Jun 20, 2015
|
Huge -1 for this, since this problem is already solved by rust-lang/rust#23895 (discussion here), which was, incidentally, proposed and implemented after this RFC was proposed. For example, this code from the RFC: fn do_something(closure: &mut FnMut(i32) -> i32) {
...
}
...
do_something(&mut |x| x*2);can be rewritten as: fn do_something<F: FnMut(i32) -> i32>(closure: F) {
...
}
...
do_something(|x| x*2);And for the case of recursive functions that take closures, I would say that explicitly writing out references and avoiding dynamic dispatch is the best, since you could accidentally change the function's asymptotic runtime otherwise. (with something similar to this) |
This comment has been minimized.
This comment has been minimized.
bluss
commented
Jun 23, 2015
|
@theemathas Letting it be caller's choice to use a trait object or not is my favourite solution, but how do you make sure |
This comment has been minimized.
This comment has been minimized.
|
@bluss using type ascription, of course! E.g. |
This comment has been minimized.
This comment has been minimized.
|
I had a hunch that this PR was catering to an obsolete need, due to seeing only a couple occurrences of There are around 200 relevant closure expressions and almost 90 Maybe that's not enough reason to stop this PR, but it personally feels like a waste a time to cater for usecases that are going extinct, and it just adds up to technical debt. The age of indirection and virtual calls has passed, rejoice the victory of |
bluss
reviewed
Jul 6, 2015
| that function be generic over the closure type (e.g., `F` where | ||
| `F:FnMut()`). For example, closure objects reduce code bloat, they | ||
| work better with object safety restrictions, and they avoid infinite | ||
| monomorphic expansion for recursion functions. |
This comment has been minimized.
This comment has been minimized.
bluss
Jul 6, 2015
In my experience, if you can use a &Fn(X) -> Y argument to avoid infinite regress in the signature for functions with recursively passed closures, you can normally use &F where F: Fn(X) -> Y the same way.
This comment has been minimized.
This comment has been minimized.
|
@eddyb you could view that as evidence that the annoyingness of writing functions that accept @bluss note that this change is equally helpful if you write @theemathas can you elaborate a bit more on the example? I don't quite see how this is solving the problem yet. The "keep adding extra levels of indirection" problem is interesting, but strongly suggests that we should encourage people to handle recursive cases by pulling the reference into the fn signature, in which case this change makes that more ergonomic. All that said, I continue to rest on a knife's edge here. Clearly this change makes the expansion of |
This comment has been minimized.
This comment has been minimized.
|
I do want it to be more convenient and desirable to use virtual dispatch, since I believe there is great opportunity for profile-based tuning of function dispatch in Rust. This seems like it helps, but I haven't looked too closely. |
This comment has been minimized.
This comment has been minimized.
|
@brson yesterday on IRC I was mentioning adding static -> dynamic dispatch conversion to LLVM's mergefunc pass (so it can merge two functions that are identical except for some calls, which can be turned into indirect calls to a parameter of those functions). @cmr informed me that there's an associated cost to indirect calls, at least on older CPU microarchitectures, mostly due to the fact that a Now that you mention profile-guided optimizations, the kind of automatic virtualization I described seems perfect if the costs have been measured already. |
This comment has been minimized.
This comment has been minimized.
|
@eddyb I am somewhat skeptical that we ought to be automatically converting to static dispatch, but regardless we want users to be able to easily throw the switch themselves. |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis What I was suggesting was optimizing away code bloat by turning static dispatch into dynamic dispatch, wherever there would be a measurable gain. That said, if do we get some sugar, I'd prefer being able to pass trait objects by value, instead. |
This comment has been minimized.
This comment has been minimized.
|
Per my last comment, I'm just going to withdraw this RFC for now. Perhaps we'll revisit this theme in the future after we've had more progress on closures in other areas. Thanks all for the comments and discussion! |
nikomatsakis commentedJan 28, 2015
Modify the || expression sugar so that it can expand to either F, &F, or &mut F, where F is a fresh struct type implementing one of the Fn/FnMut/FnOnce traits.
Rendered view.