rfcs/text/2132-copy-closures.md

5.6 KiB

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 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 Cloned 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 or Clone?