Skip to content

Commit

Permalink
Merge pull request #2132 from cramertj/copy-closures
Browse files Browse the repository at this point in the history
Copy/Clone Closures
  • Loading branch information
aturon authored Sep 11, 2017
2 parents ad6cea5 + ea28196 commit 54cc16f
Showing 1 changed file with 170 additions and 0 deletions.
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`?

0 comments on commit 54cc16f

Please sign in to comment.