Skip to content

Commit 54cc16f

Browse files
authored
Merge pull request #2132 from cramertj/copy-closures
Copy/Clone Closures
2 parents ad6cea5 + ea28196 commit 54cc16f

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

text/2132-copy-closures.md

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
- Feature Name: copy_closures
2+
- Start Date: 2017-8-27
3+
- RFC PR: https://github.com/rust-lang/rfcs/pull/2132
4+
- Rust Issue: https://github.com/rust-lang/rust/issues/44490
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Implement `Clone` and `Copy` for closures where possible:
10+
11+
```rust
12+
// Many closures can now be passed by-value to multiple functions:
13+
fn call<F: FnOnce()>(f: F) { f() }
14+
let hello = || println!("Hello, world!");
15+
call(hello);
16+
call(hello);
17+
18+
// Many `Iterator` combinators are now `Copy`/`Clone`:
19+
let x = (1..100).map(|x| x * 5);
20+
let _ = x.map(|x| x - 3); // moves `x` by `Copy`ing
21+
let _ = x.chain(y); // moves `x` again
22+
let _ = x.cycle(); // `.cycle()` is only possible when `Self: Clone`
23+
24+
// Closures which reference data mutably are not `Copy`/`Clone`:
25+
let mut x = 0;
26+
let incr_x = || x += 1;
27+
call(incr_x);
28+
call(incr_x); // ERROR: `incr_x` moved in the call above.
29+
30+
// `move` closures implement `Clone`/`Copy` if the values they capture
31+
// implement `Clone`/`Copy`:
32+
let mut x = 0;
33+
let print_incr = move || { println!("{}", x); x += 1; };
34+
35+
fn call_three_times<F: FnMut()>(mut f: F) {
36+
for i in 0..3 {
37+
f();
38+
}
39+
}
40+
41+
call_three_times(print_incr); // prints "0", "1", "2"
42+
call_three_times(print_incr); // prints "0", "1", "2"
43+
```
44+
45+
# Motivation
46+
[motivation]: #motivation
47+
48+
Idiomatic Rust often includes liberal use of closures.
49+
Many APIs have combinator functions which wrap closures to provide additional
50+
functionality (e.g. methods in the [`Iterator`] and [`Future`] traits).
51+
52+
However, closures are unique, unnameable types which do not implement `Copy`
53+
or `Clone`. This makes using closures unergonomic and limits their usability.
54+
Functions which take closures, `Iterator` or `Future` combinators, or other
55+
closure-based types by-value are impossible to call multiple times.
56+
57+
One current workaround is to use the coercion from non-capturing closures to
58+
`fn` pointers, but this introduces unnecessary dynamic dispatch and prevents
59+
closures from capturing values, even zero-sized ones.
60+
61+
This RFC solves this issue by implementing the `Copy` and `Clone` traits on
62+
closures where possible.
63+
64+
[`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
65+
[`Future`]: https://docs.rs/futures/*/futures/future/trait.Future.html
66+
67+
# Guide-level explanation
68+
[guide-level-explanation]: #guide-level-explanation
69+
70+
If a non-`move` closure doesn't mutate captured variables,
71+
then it is `Copy` and `Clone`:
72+
73+
```rust
74+
let x = 5;
75+
let print_x = || println!("{}", x); // `print_x` is `Copy + Clone`.
76+
77+
// No-op helper function which moves a value
78+
fn move_it<T>(_: T) {}
79+
80+
// Because `print_x` is `Copy`, we can pass it by-value multiple times:
81+
move_it(print_x);
82+
move_it(print_x);
83+
```
84+
85+
Non-`move` closures which mutate captured variables are neither `Copy` nor
86+
`Clone`:
87+
88+
```rust
89+
let mut x = 0;
90+
91+
// `incr` mutates `x` and isn't a `move` closure,
92+
// so it's neither `Copy` nor `Clone`
93+
let incr = || { x += 1; };
94+
95+
move_it(incr);
96+
move_it(incr); // ERROR: `print_incr` moved in the call above
97+
```
98+
99+
`move` closures are only `Copy` or `Clone` if the values they capture are
100+
`Copy` or `Clone`:
101+
102+
```rust
103+
let x = 5;
104+
105+
// `x` is `Copy + Clone`, so `print_x` is `Copy + Clone`:
106+
let print_x = move || println!("{}", x);
107+
108+
let foo = String::from("foo");
109+
// `foo` is `Clone` but not `Copy`, so `print_foo` is `Clone` but not `Copy`:
110+
let print_foo = move || println!("{}", foo);
111+
112+
// Even closures which mutate variables are `Clone + Copy`
113+
// if their captures are `Clone + Copy`:
114+
let mut x = 0;
115+
116+
// `x` is `Clone + Copy`, so `print_incr` is `Clone + Copy`:
117+
let print_incr = move || { println!("{}", x); x += 1; };
118+
move_it(print_incr);
119+
move_it(print_incr);
120+
move_it(print_incr);
121+
```
122+
123+
# Reference-level explanation
124+
[reference-level-explanation]: #reference-level-explanation
125+
126+
Closures are internally represented as structs which contain either values
127+
or references to the values of captured variables
128+
(`move` or non-`move` closures).
129+
A closure type implements `Clone` or `Copy` if and only if the all values in
130+
the closure's internal representation implement `Clone` or `Copy`:
131+
132+
- Non-mutating non-`move` closures only contain immutable references
133+
(which are `Copy + Clone`), so these closures are `Copy + Clone`.
134+
135+
- Mutating non-`move` closures contain mutable references, which are neither
136+
`Copy` nor `Clone`, so these closures are neither `Copy` nor `Clone`.
137+
138+
- `move` closures contain values moved out of the enclosing scope, so these
139+
closures are `Clone` or `Copy` if and only if all of the values they capture
140+
are `Clone` or `Copy`.
141+
142+
The internal implementation of `Clone` for non-`Copy` closures will resemble
143+
the basic implementation generated by `derive`, but the order in which values
144+
are `Clone`d will remain unspecified.
145+
146+
# Drawbacks
147+
[drawbacks]: #drawbacks
148+
149+
This feature increases the complexity of the language, as it will force users
150+
to reason about which variables are being captured in order to understand
151+
whether or not a closure is `Copy` or `Clone`.
152+
153+
However, this can be mitigated through error messages which point to the
154+
specific captured variables that prevent a closure from satisfying `Copy` or
155+
`Clone` bounds.
156+
157+
# Rationale and Alternatives
158+
[alternatives]: #alternatives
159+
160+
It would be possible to implement `Clone` or `Copy` for a more minimal set of
161+
closures, such as only non-`move` closures, or non-mutating closures.
162+
This could make it easier to reason about exactly which closures implement
163+
`Copy` or `Clone`, but this would come at the cost of greatly decreased
164+
functionality.
165+
166+
# Unresolved questions
167+
[unresolved]: #unresolved-questions
168+
169+
- How can we provide high-quality, tailored error messages to indicate why a
170+
closure isn't `Copy` or `Clone`?

0 commit comments

Comments
 (0)