diff --git a/text/2132-copy-closures.md b/text/2132-copy-closures.md new file mode 100644 index 00000000000..53751b49344 --- /dev/null +++ b/text/2132-copy-closures.md @@ -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: 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(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) {} + +// 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`?