5.6 KiB
- Feature Name:
copy_closures
- Start Date: 2017-08-27
- RFC PR: rust-lang/rfcs#2132
- Rust Issue: rust-lang/rust#44490
Summary
Implement Clone
and Copy
for closures where possible:
// 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
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.
Guide-level explanation
If a non-move
closure doesn't mutate captured variables,
then it is Copy
and Clone
:
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
:
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
:
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
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 areCopy + Clone
), so these closures areCopy + Clone
. -
Mutating non-
move
closures contain mutable references, which are neitherCopy
norClone
, so these closures are neitherCopy
norClone
. -
move
closures contain values moved out of the enclosing scope, so these closures areClone
orCopy
if and only if all of the values they capture areClone
orCopy
.
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
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
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
- How can we provide high-quality, tailored error messages to indicate why a
closure isn't
Copy
orClone
?