I've been thinking about this on and off.
On Sat, Jan 25, 2014 at 6:31 AM, Niko Matsakis <n...@alum.mit.edu> wrote: > This is good to think about, though I think writing `&mut || foo` > feels like a nonstarter to me. > I agree. > > I still feel that `&my` -- meaning, a pointer that gives ownership of > the referent but not the memory where the referent lives -- is the > right approach here. Basically, the type of `|| ...` would be `&my T` > where `T` is some fresh type that implements `Fn<U,R>`. > I prefer the name `&move`: it's a natural progression on `&mut`, which confers permission to mut[ate], while `&move` also confers permission to move. When creating one, I think `&move some_foo` (move out of my_foo) is also clearer than `&my some_foo`. Plus it feels weird for the stronger one to have a shorter name. > > Now, the consumer of the closure could be any of: > > - `fn foo(x: |U| -> R)`: In this case, the type `|U| -> R` > is equivalent to `&mut Fn<U,R>`. This is an automatic > object coercion and also an automatic reborrow. That is, > written out very explicitly, `foo(|u| r)` would be > equivalen to `foo((&mut *(&my T { ... })) as &mut Fn<U,R>)`, > where again `T` is the anonymous closure type. > > - `fn foo<T:Fn<U,R>>(x: &my T) -> T`: Now `foo` takes ownership of the > value `T`. Because it's an `&my` pointer, `foo()` can move the > referent around. > > - `fn foo<T:Fn<U,R>>(x: &mut T) -> T`: `foo` does not take ownership > of the closure, but does avoid virtual dispatch. > If you wanted to pass an unboxed closure without indirection though, like `fn foo<T: Fn<U, R>>(x: T)`, then you would have to explicitly dereference the closure, i.e. `foo(*|u| r)` (which might be OK). > > This can be extended to "once closures" in a pretty straightforward > way: > > - `fn foo(x: once |U| -> R)` > - `fn foo<T:OnceFn<U,R>>(x: &my T)` > The plan I had been thinking of is similar in some ways, different in others, and likely has different advantages and drawbacks. The basic problem is that there's a zoo of closure types: http://glaebhoerl.tumblr.com/rust_closure_types (I will be referring to things from this in the following!) and it's difficult to have convenient and consistent syntax for all of them at the same time. There's also several different areas of the language which we want to all work ergonomically: writing closures, invoking closures, using higher-order functions, and writing the types of closures. I'll go through these in turn. Anonymous closure literals, `|args| foo`: I was thinking these would represent an unboxed closure. That seems like the most straightforward thing (and the same as C++). This raises two questions: - Which traits does it implement? Having to explicitly annotate it somehow would be onerous, so I think this should be inferred from the way the body of the closure uses captured variables. What's not totally clear to me is how to handle the case where you have an `&move FnOnce`, but only some of the captured variables are moved out of by the closure body: presumably the programmer would expect the remainder to remain available afterwards. - If a HOF expects a stack closure, would you have to write e.g. `&mut |args| foo`? Again, this feels onerous. On the other hand, when making a heap closure, having to write e.g. `~|args| foo` explicitly is actually desirable. The least bad solution I can think of is that anonymous closures would auto-borrow to `&`, `&mut`, or `&move`. Invoking a closure also raises questions: does `foo()` imply using `call()` from `Fn`, `call_mut()` from `FnMut`, or `call_once()` from `FnOnce`? Once again the least bad solution I can think of is that there would be a bit of magic, and it would select the "best" option (the one least restrictive on the caller) from the ones in scope (so Fn > FnMut > FnOnce). Finally there's the question of how to write their types. The raw trait-based syntax, `&mut FnMut<(int, int), int>`, is just awful. The current approach the language takes is to have syntax sugar for a couple of types (`&mut FnMut` is `|args| -> foo` and `~FnOnce` is `proc`), but this leaves a lot of other equally legitimate types out in the cold. And if a closure literal `|args| foo` denotes an unboxed closure, it would be weird for the same thing at the type level to mean something different. On the other hand, there's too many useful types to provide dedicated syntax for all of them. Here the least bad option I managed to think of is to introduce a new syntax for writing generic types: `Foo(A, B) -> C` would be equivalent to either `Foo<(A, B), C>` or `Foo<C, A, B>` depending on how variadics end up working. I don't think this would introduce any ambiguities: parentheses in types denote tuples, and it's not currently legal to follow an identifier with a tuple type. (Correct me if I'm wrong.) This syntax would be available both when declaring a generic type and when referring to one. (To avoid confusion, we might want to require the same syntax be used consistently for a given type, so that if you declare it with function-like syntax, you also have to refer to it that way, and vice versa.) Then the programmer can write decent-looking typedefs for whichever closure types she will be using frequently. The standard library could provide a few basic ones: // yes, it would also work for traits! trait Fn(Args...) -> Ret { ... } trait FnMut(Args...) -> Ret { ... } trait FnOnce(Args...) -> Ret { ... } // so `MutFn(int, int) -> int` is the new `|int, int| -> int`, a slight downgrade, in exchange for much flexibility elsewhere. type MutFn(Args...) -> Ret = &mut FnMut(Args...) -> Ret; type OnceFn(Args...) -> Ret = &move FnOnce(Args...) -> Ret; // Would this still be important to have next to `OnceFn`? Anyway, it's cute. type Proc(Args...) -> Ret = ~FnOnce(Args...) -> Ret; > > > Niko > > On Mon, Dec 30, 2013 at 07:31:45PM -0800, Patrick Walton wrote: > > Yes, it would need to be &mut, you're right. > > > > I think the underlying type syntax would be something like > `Fn<int,&int>` for the unboxed version, and `&mut Fn<int,&int>` for the > boxed version. The type syntax with the bars is just syntactic sugar for > the latter (and, in trait bound position, for the former). > > > > It's somewhat unfortunate but I don't see a particularly good > alternative if we want boxed and unboxed closures alike to have > nice-looking APIs. The alternative, I guess, is to block 1.0 on unboxed > closures, convert all our APIs to unboxed closures where possible, and just > say that if you want a boxed closure you have to write `&mut |x| x + 1` at > each closure construction site... > > > > Patrick > > > > "Gábor Lehel" <glaebho...@gmail.com> wrote: > > >Wouldn't it have to be `&mut` rather than `&` to fit the semantics of | > > >|, > > >which is affine and can mutate its environment? > > > > > >And wouldn't this lead to divergence between the type- and value > > >syntax, > > >with | | as a type being a boxed closure (`&mut FnMut`), and an unboxed > > >closure as a value? This was one of the nicer points of the recent > > >closure > > >overhaul, and it would be a shame to lose it so soon. > > > > > > > > >On Mon, Dec 30, 2013 at 10:11 PM, Patrick Walton > > ><pcwal...@mozilla.com>wrote: > > > > > >> I've been thinking that to future-proof unboxed closures in the > > >future we > > >> should maybe limit `|x| x+1` lambda syntax to either (a) require `&` > > >in > > >> front or (b) in function position. > > >> > > >> So today you would write: > > >> > > >> let f = |x| x+1; > > >> > > >> But tomorrow you would write: > > >> > > >> let f = &|x| x+1; > > >> > > >> But it would always work here: > > >> > > >> v.map(|&x| x+1); > > >> > > >> The reason is simply that we'd like `|x| x+1` to become an unboxed > > >closure > > >> in the future and it's easier in the language semantics to > > >future-proof for > > >> it this way: we simply special-case the function argument position. > > >> > > >> Alternatively we can do it with assignability: say that `|x| x+1` is > > >an > > >> anonymous type (an error today) that is assignable to the type > > >> `|int|->int`. That might be cleaner than special-casing the function > > >> argument position. > > >> > > >> Patrick > > >> _______________________________________________ > > >> Rust-dev mailing list > > >> Rust-dev@mozilla.org > > >> https://mail.mozilla.org/listinfo/rust-dev > > >> > > > > -- > > Sent from my Android phone with K-9 Mail. Please excuse my brevity. > > > _______________________________________________ > > Rust-dev mailing list > > Rust-dev@mozilla.org > > https://mail.mozilla.org/listinfo/rust-dev > >
_______________________________________________ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev