-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2132 from cramertj/copy-closures
Copy/Clone Closures
- Loading branch information
Showing
1 changed file
with
170 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`? |