-
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
Generic closures #1650
Generic closures #1650
Changes from 4 commits
82b65b9
76d3b2a
39a89aa
5129c4e
cbbe447
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
- Feature Name: generic_closure | ||
- Start Date: 2015-06-15 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
This RFC adds the ability to define closures that are generic over types. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Generic closures can be used to support compound operations on tuple types: | ||
|
||
```rust | ||
#[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||
struct Tuple<A, B, C>(pub A, pub B, pub C); | ||
|
||
impl<A, B, C> Tuple<A, B, C> { | ||
fn map<A2, B2, C2, F>(self, mut f: F) -> Tuple<A2, B2, C2> | ||
where F: FnMut(A) -> A2 + FnMut(B) -> B2 + FnMut(C) -> C2 | ||
{ | ||
Tuple(f(self.0), f(self.1), f(self.2)) | ||
} | ||
|
||
fn fold<T, F>(self, val: T, mut f: F) -> T | ||
where F: FnMut(T, A) -> T + FnMut(T, B) -> T + FnMut(T, C) -> T | ||
{ | ||
let val = f(val, self.0); | ||
let val = f(val, self.1); | ||
let val = f(val, self.2); | ||
val | ||
} | ||
} | ||
|
||
let a = Tuple(1u8, 2u32, 3.5f32).map(<T: Into<f64>>|x: T| x.into() + 1.0); | ||
assert_eq!(a, (2f64, 3f64, 4.5f64)); | ||
|
||
let b = Tuple(1u8, 2u32, 3.5f32).fold(10.0, <T: Into<f64>>|x, y: T| x + y.into()); | ||
assert_eq!(b, 16.5f64); | ||
``` | ||
|
||
A fully working example of this code (with manually implemented closures) can be found [here](https://play.rust-lang.org/?gist=ea867336945253752e31873fc752ec06&version=nightly&backtrace=0). | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
## Syntax | ||
|
||
There are two ways to specify generic bounds on closures: | ||
|
||
```rust | ||
<T: Debug>|x: T| println!("{:?}", x); | ||
|
||
<T>|x: T| where T: Debug { | ||
println!("{:?}", x); | ||
} | ||
``` | ||
|
||
When using the `where` syntax, the braces around the closure body are mandatory. | ||
|
||
If the `move` keyword is used then it must appear before the generic parameter list: | ||
|
||
```rust | ||
move <T: Debug>|x: T| println!("{:?}", x); | ||
|
||
move <T>|x: T| where T: Debug { | ||
println!("{:?}", x); | ||
} | ||
``` | ||
|
||
All generic parameters must be used in the closure argument list. This is necessary to ensure that the closure can implement all the required `Fn` traits. | ||
|
||
## Implementation | ||
|
||
The generated closure type will have generic implementations of `Fn`, `FnMut` and `FnOnce` with the provided type bounds. This is similar to the way closures currently have generic implementations over lifetimes. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way that closures are generic over lifetimes corresponds to types of the form Is that analogy what you intend to apply here, keeping in mind that the above lifetimes are erased at compile time? I ask because when I first saw this proposal, I had assumed that part of the intention was to enable one to write rank-N higher-order functions (think A concrete example of what I mean: fn hof_example<F>(f: F) -> i32
where F: for <T: fmt::Debug> Fn(T) -> i32
{
f(0.0) + f(true)
} Note: the above is certainly doable under a monomorphization strategy.
I'm just trying to understand the scope of what you're proposing. In other words: Can you speak more on the actual type assigned to these generic closures, or at least about the generic implementations here? I.e. what type are you assigning to the expression <T: Debug> |x: T| { println!("{:?}, x); } and what impls are generated for it? I'm trying to figure out if this ends up being something like: impl<T: Debug> Fn(T) for [closure@pnk-example] { ... } or if there is something broader being implicitly proposed here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The closure is as generic as the function that created it, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (after reading the comment thread a bit, it seems like rank-N types are not what is being proposed here. I still think the RFC text must be clarified to make that more clear.) |
||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am suspicious of any proposal that has zero drawbacks, alternatives, and unanswered questions. |
||
|
||
Increased language complexity. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
If the given syntax is determined to be ambiguous, this one can be used instead: | ||
|
||
```rust | ||
for<T: Debug>|x: T| println!("{:?}", x); | ||
|
||
for<T>|x: T| where T: Debug { | ||
println!("{:?}", x); | ||
} | ||
``` | ||
|
||
We could just not add this, however it would make generic operations on tuples less ergonomic. This feature is going to be even more useful when variadic generics are added in the future. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
What are the syntax interactions of `move` generic closures with the proposed `&move` reference type? | ||
|
||
Is the syntax in this RFC ambiguous for the parser? |
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.
Are these bounds required? Can they be inferred?
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.
From the discussions on IRC, it seems that higher-ranked inference is very hard and/or undecidable. I went for the conservative option of requiring bounds for generic closures.
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.
Rust has never inferred generic bounds, that sounds like C++ :)
On Jun 15, 2016 2:30 PM, "Steven Fackler" notifications@github.com wrote: