Skip to content
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

Closed
wants to merge 5 commits into from
Closed
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions text/0000-generic-closures.md
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);
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Contributor

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:

In text/0000-generic-closures.md
#1650 (comment):

+let b = Tuple(1u8, 2u32, 3.5f32).fold(10.0, <T: Into>|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);

Are these bounds required? Can they be inferred?


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rfcs/pull/1650/files/82b65b9bf80f6cecbded445c08df5560c359a6df#r67218397,
or mute the thread
https://github.com/notifications/unsubscribe/AAC3n6w3dDkkTfbTArkT4MoVbsMItwkZks5qMETEgaJpZM4I2Wcq
.


<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.
Copy link
Member

@pnkfelix pnkfelix Jun 28, 2016

Choose a reason for hiding this comment

The 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 for <'a> Fn(&'a u8) (for example), where the client code can instantiate that type at different lifetimes for 'a.

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 forall in Haskell), where a function passed as an argument is able to be instantiated at multiple concrete types.

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.

  • It becomes ... harder when one tries to generalize it to object-types (f: Box<for <T: fmt::Debug> Fn(T)>).

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closure is as generic as the function that created it, the impl can be more generic (with the usual requirement that the additional parameters can be deduced from the trait parameter types).

Copy link
Member

@pnkfelix pnkfelix Jun 28, 2016

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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?