-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Filling in the details around unboxed closures #97
Conversation
|
||
# Unresolved questions | ||
|
||
It remains to be seen how this interacts with not being able to use "for-all" quantifiers in trait objects. This will break some code until/unless we introduce this capability. How much is unknown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean exactly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't use a trait as a region binding site. <'a>Trait<'a>
doesn't work.
The prior RFC mentions that it would not be possible to return closures from functions without implementing "unboxed trait objects". Does this proposal avoid the need for that? |
I think the same rationale behind making
|
(wouldn't immutable default be more consistent? .. unless you see a 'mut' keyword somewhere, its immutable - I personally wouldn't mind writing 'Mut' more often , even if mutable closures are more common, for the more consistent rule of things being immutable by default- marking the side effects/outputs more clearly in function signatures - everything without 'mut' is an input) |
I agree that explicit mut is often better, but I worry that if we default to immutable then people writing functions that take closure arguments will often forget to add the &mut and not realize anything is wrong. Which is to say, taking a &mut closure is the most permissive type of closure to take, so making it the default seems reasonable. |
@bachm, I the appeal of anonymous functions is a lightweight syntax. The current proposal for @dobkeratops, our old closures were already mutable, so this isn't a change in philosophy. I could only see us making this change if measurements showed that mutable closures and immutable closures were roughly equal in frequency. |
So we would gain much clearer names in exchange for a significantly longer name for the (most likely) least used closure type. |
Using any of those turns them into keywords, which precludes using them as trait names. |
Really? Won't returning, say, BTW, I second the proposal for lifting literal syntax to type level. The type above looks much nicer when written as |
I can't say for certain without measuring, but intuitively I'd disagree about their frequency. If you want to use tasks at all, you're going to be using FnOnce all over the place. I fully admit that I share your concern that |
@netvl, I asked @thestinger to elaborate on the idea that returning an unboxed closure would not yet be possible. He confirms that this proposal alone does not yet allow for that possibility. See https://botbot.me/mozilla/rust-internals/2014-05-29/?msg=15382415&page=3 for a transcript of the discussion. |
Returning a boxed closure like C++14 allows returning unboxed closures directly and another RFC could extend the type system to permit it, along with returning something like an unboxed |
@bstrie, ah, I understand. Somehow I thought that under "closures" you meant "any closures", and that's indeed would be very unfortunate. I thought that trait objects will be covered by DST change, but it is the whole point of DST - their size is not known, so they only can be accessed through a pointer. Won't any kind of unboxed trait object conflict with DST? |
BTW, I think that |
@netvl: DST is unrelated to this issue. The issue is the inability to return a generic type implementing a specific trait. It doesn't need to be discussed in detail here, as it's a separate proposal. |
One idea I meant to mention: there isn't much need for an explicit choice between the 3 flavors in the closure expression: there is a simple subtyping relationship between the 3, and the set of `The rules are as such:
(they don't seem as simple in that form, feel free to suggest a better representation) |
@eddyb, I don't quite understand. If all closures are FnOnce, would it be impossible to ever call any closure twice? |
All closures can be |
@bstrie what @eddyb is saying is basically:
Therefore it follows:
Ergo:
In short form:
|
I must be misunderstanding something. Is |
@bstrie hm. That's a good point. I'll need to think about it some more. |
@bstrie A literal translation of a Sendability is somewhat orthogonal to which trait is implemented. |
@zkamsler Well, the general form of @bstrie The tricky bit is remembering that If we pass the environment by value to the body of the closure, then the environment will be consumed (i.e. If we've loaned the environment out mutably/uniquely (i.e. created a If we've loaned the environment out immutably/aliased (i.e. created a The usual rules for borrows apply. We can't call a closure by value while references exist. A closure that mutates its environment only has one path that can call it at a time (by value, a chain of A closure's copy/move properties are going to be determined by its environment. If everything in the environment is Does that clear things up for you? |
It does, thanks. It's still not the most intuitive subtyping relationship, but if it both gives our closures greater flexibility and allows us to omit which type of closure it is at the declaration site, I think it would be worth it. However, I get the impression that @eddyb's proposal augments this RFC, rather than supplanting it. Perhaps it deserves its own RFC, contingent on this one? |
I think it should be possible to unambiguously determine which trait(s) an anonymous closure can implement based on its body. Does it move out of its captured environment? If so, it can only be Under @pcwalton's proposal as written, this could be used as the default for the un-annotated Under the proposal as amended by @eddyb's suggestion, which I support (and might be partly re-stating), closures could instead implement all of the relevant traits: the one determined by the method above, and also all of its ("looser") super-traits. This could make explicit trait annotation syntax unnecessary. (To completely obviate it, it might also be necessary to automatically coerce trait objects to super-trait objects, which is desirable on its own terms, but I'm not sure what the status of it is. ) The other question that has to be answered under this scheme is what to do when calling such a closure. If it implements both |
(thanks everyone for expanding on my initial suggestion) Keep in mind that you do not really want to have @anasazi's explicit inheritance scheme. |
Closing in favor of the most recent unboxed closures RFC #114 |
Summary
Unboxed closures should be implemented with three traits (
Fn
,FnMut
, andFnOnce
), and there should be a leading sigil (&:
/&mut:
/:
) before the argument list so the programmer can describe which one is meant.Motivation
This RFC simply addresses some points that were not ironed out in the previous unboxed closure RFC.
Detailed design
This builds on RFC #77 "unboxed closures"; see the design for that.
There should be three traits as lang items:
The unboxed closure literal form
|a, b| a + b
creates an anonymous structure implementing one of the above three traits. Accordingly, we introduce new syntaxes for unboxed closures to correspond to the three traits above:Once boxed closures are removed, the regular
|a, b| a + b
syntax will be an alias for|&mut: a, b| a + b
, since that is the commonest trait to implement.The idea behind the syntax is that what goes before the
:
mirrors what goes beforeself
in thecall
/call_fn
/call_once
function signature. This syntax avoids introducing any new keywords to the language.The call operator
x(y, z)
will desugar to one ofx.Fn::call_fn((y, z))
,x.FnMut::call((y, z))
, andx.FnOnce::call_once((y, z))
, depending on the trait thatx
implements. Ifx
implements more than one ofFn
/FnMut
/FnOnce
, then the compiler reports an error and thex(y, z)
form cannot be used.We will remove
proc(A...) -> R
and replace withBox<FnOnce<(A...),R>>
.Drawbacks
Fn
andFnOnce
are too much complexity.Alternatives
The impact of not doing this at all is that the precise trait that unboxed closures implement will be undefined, and we will continue to have
proc
.An alternative to tupling arguments is to introduce variadic generics, but that seems like a lot of complexity.
Unresolved questions
It remains to be seen how this interacts with not being able to use "for-all" quantifiers in trait objects. This will break some code until/unless we introduce this capability. How much is unknown.
ABI issues relating to tupling struct arguments on uncommon architectures like MIPS and non-EABI ARM have been inadequately explored.