Skip to content

Commit e50f4de

Browse files
Add copy ergonomics design note
1 parent e2baded commit e50f4de

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@
2929
- [Auto traits](./design_notes/auto_traits.md)
3030
- [Eager drop](./design_notes/eager_drop.md)
3131
- [Autoderef and autoref in operators](./design_notes/autoref_ops.md)
32+
- [Copy type ergonomics](./design_notes/copy_ergonomics.md)

src/design_notes/copy_ergonomics.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copy type ergonomics
2+
3+
## Background
4+
5+
There are a number of pain points with `Copy` types that the lang team is
6+
interested in exploring, though active experimentation is not currently ongoing.
7+
8+
Some key problems are:
9+
10+
## `Copy` cannot be implemented with non-`Copy` members
11+
12+
There are standard library types where the lack of a `Copy` impl is an
13+
active pain point, e.g., [`MaybeUninit`](https://github.com/rust-lang/rust/issues/62835)
14+
and [`UnsafeCell`](https://github.com/rust-lang/rust/issues/25053).
15+
16+
### History
17+
18+
* `unsafe impl Copy for T` which avoids the requirement that T is recursively
19+
Copy, but is obviously unsafe.
20+
* https://github.com/rust-lang/rust/issues/25053#issuecomment-218610508
21+
* `Copy` is dangerous on types like `UnsafeCell` where `&UnsafeCell<T>`
22+
otherwise would not permit access to `T` in [safe
23+
code](https://github.com/rust-lang/rust/issues/25053#issuecomment-98447164).
24+
25+
## `Copy` types can be (unintentionally) copied
26+
27+
Even if a type is Copy (e.g., `[u8; 1024]`) it may not be a good idea to make
28+
use of that in practice, since copying large amounts of data is slow. This is
29+
primarily a performance concern, so the problem is usually that these copies are
30+
easy to miss. However, depending on the size of the buffer, it can also be a
31+
correctness concern as it may cause an unintended stack overflow with too many
32+
accidental copies.
33+
34+
Should we want to lint on this code, deciding on a size threshold may be
35+
difficult. It's not generally possible for the compiler to know whether a
36+
particular copy operation is likely to lead to stack overflow or undesirable
37+
performance. We don't have examples yet of cases where there's desirable large
38+
copies (that should not be linted against) or concrete cases where the copies
39+
are accidental; collecting this information would be worthwhile.
40+
41+
Implementations of `Copy` on closures and arrays are the prime example of Rust
42+
currently being overeager with the defaults in some contexts.
43+
44+
This also comes up with `Copy` impls on `Range`, which would generally be
45+
desirable but is error-prone given the `Iterator/IntoIterator` impls on ranges.
46+
47+
The example here does not compile today (since Range is not Copy), but would be
48+
unintuitive if it did.
49+
50+
```rust,compile_fail
51+
let mut x = 0..10;
52+
let mut c = move || x.next();
53+
println!("{:?}", x.next()); // prints 0
54+
println!("{:?}", c()); // prints 0, because the captured x is implicitly copied.
55+
```
56+
57+
This example illustrates the range being copied into the closure, while the user
58+
may have expected the name "x" to refer to the same range in both cases.
59+
60+
The move keyword here likely disambiguates this particular case for users, but
61+
in closures with more captures it may be not as obvious that the range type in
62+
particular was copied in.
63+
64+
A lint has been [proposed](https://github.com/rust-lang/rust/issues/45683) to
65+
permit Copy impls on types where Copy is likely not desirable with particular
66+
conditions (e.g., Copy of IntoIterator-implementing types after iteration).
67+
68+
Note that "large copies" comes up with moves as well (which are copies, just
69+
taking ownership as well), so a size-based lint is plausibly desirable for both.
70+
71+
### History
72+
73+
* Proposed lint: [#45683](https://github.com/rust-lang/rust/issues/45683)
74+
75+
## References to `Copy` types
76+
77+
Frequently when dealing with code generic over T you end up needing things like
78+
`[u8]::contains(&5)` which is ugly and annoying. Iterators of copy types also
79+
produce `&&u64` and similar constructs which can produce unexpected type errors.
80+
81+
```rust
82+
for x in &vec![1, 2, 3, 4, 5, 6, 7] {
83+
process(*x); // <-- annoying that we need `*x`
84+
}
85+
86+
fn process(x: i32) { }
87+
```
88+
89+
```rust
90+
fn sum_even(v: &[u32]) -> u32 {
91+
// **v is annoying
92+
v.iter().filter(|v| **v % 2 == 0).sum()
93+
}
94+
```
95+
96+
Note that this means that you in most cases want to "boil down" to the inner
97+
type when dealing with references, i.e., `&&u32` you actually want `u32`, not
98+
`&u32`. Notably, though, this may *not* be true if the Copy type is something
99+
more complex (e.g., a future Copy Cell), since then `&Cell` is quite different
100+
from a `Cell`, the latter being likely useless for modification at least.
101+
102+
There is also plausibly performance left on the table with types like `&&u64`.
103+
104+
Note that this interacts with the unintentional copies (especially of large
105+
structures).
106+
107+
This could plausibly be done with moved values as well, so long as the
108+
semantics match the syntax (e.g. `wants_ref(foo)` acts like `wants_ref(&{foo})`)
109+
similar to how one can pass `&mut` to something that only wants `&`.
110+
111+
### History
112+
113+
* [RFC 2111 (not merged)](https://github.com/rust-lang/rfcs/pull/2111)
114+
* [Rust tracking issue (closed)](https://github.com/rust-lang/rust/issues/44763)
115+
* "Allow owned values where references are expected" in [rust-roadmap-2017#17](https://github.com/rust-lang/rust-roadmap-2017/issues/17)

0 commit comments

Comments
 (0)