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

Copy/Clone Closures #2132

Merged
merged 2 commits into from
Sep 11, 2017
Merged
Changes from all 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
170 changes: 170 additions & 0 deletions text/2132-copy-closures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
- Feature Name: copy_closures
- Start Date: 2017-8-27
- RFC PR: https://github.com/rust-lang/rfcs/pull/2132
- Rust Issue: https://github.com/rust-lang/rust/issues/44490

# Summary
[summary]: #summary

Implement `Clone` and `Copy` for closures where possible:

```rust
// Many closures can now be passed by-value to multiple functions:
fn call<F: FnOnce()>(f: F) { f() }
let hello = || println!("Hello, world!");
call(hello);
call(hello);

// Many `Iterator` combinators are now `Copy`/`Clone`:
let x = (1..100).map(|x| x * 5);
let _ = x.map(|x| x - 3); // moves `x` by `Copy`ing
let _ = x.chain(y); // moves `x` again
let _ = x.cycle(); // `.cycle()` is only possible when `Self: Clone`

// Closures which reference data mutably are not `Copy`/`Clone`:
let mut x = 0;
let incr_x = || x += 1;
call(incr_x);
call(incr_x); // ERROR: `incr_x` moved in the call above.

// `move` closures implement `Clone`/`Copy` if the values they capture
// implement `Clone`/`Copy`:
let mut x = 0;
let print_incr = move || { println!("{}", x); x += 1; };

fn call_three_times<F: FnMut()>(mut f: F) {
for i in 0..3 {
f();
}
}

call_three_times(print_incr); // prints "0", "1", "2"
call_three_times(print_incr); // prints "0", "1", "2"
```

# Motivation
[motivation]: #motivation

Idiomatic Rust often includes liberal use of closures.
Many APIs have combinator functions which wrap closures to provide additional
functionality (e.g. methods in the [`Iterator`] and [`Future`] traits).

However, closures are unique, unnameable types which do not implement `Copy`
or `Clone`. This makes using closures unergonomic and limits their usability.
Functions which take closures, `Iterator` or `Future` combinators, or other
closure-based types by-value are impossible to call multiple times.

One current workaround is to use the coercion from non-capturing closures to
`fn` pointers, but this introduces unnecessary dynamic dispatch and prevents
closures from capturing values, even zero-sized ones.

This RFC solves this issue by implementing the `Copy` and `Clone` traits on
closures where possible.

[`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
[`Future`]: https://docs.rs/futures/*/futures/future/trait.Future.html

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

If a non-`move` closure doesn't mutate captured variables,
then it is `Copy` and `Clone`:

```rust
let x = 5;
let print_x = || println!("{}", x); // `print_x` is `Copy + Clone`.

// No-op helper function which moves a value
fn move_it<T>(_: T) {}

// Because `print_x` is `Copy`, we can pass it by-value multiple times:
move_it(print_x);
move_it(print_x);
```

Non-`move` closures which mutate captured variables are neither `Copy` nor
`Clone`:

```rust
let mut x = 0;

// `incr` mutates `x` and isn't a `move` closure,
// so it's neither `Copy` nor `Clone`
let incr = || { x += 1; };

move_it(incr);
move_it(incr); // ERROR: `print_incr` moved in the call above
```

`move` closures are only `Copy` or `Clone` if the values they capture are
`Copy` or `Clone`:

```rust
let x = 5;

// `x` is `Copy + Clone`, so `print_x` is `Copy + Clone`:
let print_x = move || println!("{}", x);

let foo = String::from("foo");
// `foo` is `Clone` but not `Copy`, so `print_foo` is `Clone` but not `Copy`:
let print_foo = move || println!("{}", foo);

// Even closures which mutate variables are `Clone + Copy`
// if their captures are `Clone + Copy`:
let mut x = 0;

// `x` is `Clone + Copy`, so `print_incr` is `Clone + Copy`:
let print_incr = move || { println!("{}", x); x += 1; };
move_it(print_incr);
move_it(print_incr);
move_it(print_incr);
```

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

Closures are internally represented as structs which contain either values
or references to the values of captured variables
(`move` or non-`move` closures).
A closure type implements `Clone` or `Copy` if and only if the all values in
the closure's internal representation implement `Clone` or `Copy`:

- Non-mutating non-`move` closures only contain immutable references
(which are `Copy + Clone`), so these closures are `Copy + Clone`.

- Mutating non-`move` closures contain mutable references, which are neither
`Copy` nor `Clone`, so these closures are neither `Copy` nor `Clone`.

- `move` closures contain values moved out of the enclosing scope, so these
closures are `Clone` or `Copy` if and only if all of the values they capture
are `Clone` or `Copy`.

The internal implementation of `Clone` for non-`Copy` closures will resemble
the basic implementation generated by `derive`, but the order in which values
are `Clone`d will remain unspecified.

# Drawbacks
[drawbacks]: #drawbacks

This feature increases the complexity of the language, as it will force users
to reason about which variables are being captured in order to understand
whether or not a closure is `Copy` or `Clone`.

However, this can be mitigated through error messages which point to the
specific captured variables that prevent a closure from satisfying `Copy` or
`Clone` bounds.

# Rationale and Alternatives
[alternatives]: #alternatives

It would be possible to implement `Clone` or `Copy` for a more minimal set of
closures, such as only non-`move` closures, or non-mutating closures.
This could make it easier to reason about exactly which closures implement
`Copy` or `Clone`, but this would come at the cost of greatly decreased
functionality.

# Unresolved questions
[unresolved]: #unresolved-questions

- How can we provide high-quality, tailored error messages to indicate why a
closure isn't `Copy` or `Clone`?