Skip to content

Commit 9b289ac

Browse files
authored
Merge pull request #31 from llogiq/mem-replace
new idiom: mem::replace
2 parents 3d748b1 + 547ff18 commit 9b289ac

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ language.
2222
* [Iterating over an `Option`](idioms/option-iter.md)
2323
* TODO `Default` trait
2424
* [Pass variables to closure](idioms/pass-var-to-closure.md)
25+
* [`mem::replace(_)` to avoid needless clones](idioms/mem-replace.md)
2526

2627
### Design patterns
2728

idioms/mem-replace.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# `mem::replace` to keep owned values in changed enums
2+
3+
## Description
4+
5+
Say we have a `&mut MyEnum` which has (at least) two variants,
6+
`A { name: String, x: u8 }` and `B { name: String }`. Now we want to change
7+
`MyEnum::A` to a `B` if `x` is zero, while keeping `MyEnum::B` intact.
8+
9+
We can do this without cloning the `name`.
10+
11+
## Example
12+
13+
```rust
14+
use std::mem;
15+
16+
fn a_to_b(e: &mut MyEnum) {
17+
// we mutably borrow `e` here. This precludes us from changing it directly
18+
// as in `*e = ...`, because the borrow checker won't allow it. Of course
19+
// we could just take a reference to `name` and clone that, but why pay an
20+
// extra allocation for something we already have?
21+
let have_name = match *e {
22+
MyEnum::A { ref mut name, x } if x == 0 => {
23+
// this takes out our `name` and put in an empty String instead
24+
// note that empty strings don't allocate
25+
Some(mem::replace(name, "".to_string()))
26+
}
27+
// nothing to do in all other cases
28+
_ => None
29+
};
30+
// the mutable borrow ends here, so we can change `e`
31+
if let Some(name) = have_name { *e = MyEnum::B { name: name } }
32+
}
33+
```
34+
35+
36+
## Motivation
37+
38+
When working with enums, we may want to change an enum value in place, perhaps
39+
to another variant. This is usually done in two phases to keep the borrow
40+
checker happy. In the first phase, we observe the existing value and look at
41+
its parts to decide what to do next. In the second phase we may conditionally
42+
change the value (as in the example above).
43+
44+
The borrow checker won't allow us to take out `name` of the enum (because
45+
*something* must be there. We could of course `.clone()` name and put the clone
46+
into our `MyEnum::B`, but that would be an instance of the [Clone to satisfy
47+
the borrow checker] antipattern. Anyway, we can avoid the extra allocation by
48+
changing `e` with only a mutable borrow.
49+
50+
`mem::replace` lets us swap out the value, replacing it with something else. In
51+
this case, we put in an empty `String`, which does not need to allocate. As a
52+
result, we get the original `name` *as an owned value*. We can wrap this in
53+
an `Option` or another enum that we can destructure in the next step to put the
54+
contained values into our mutably borrowed enum.
55+
56+
## Advantages
57+
58+
Look ma, no allocation! Also you may feel like Indiana Jones while doing it.
59+
60+
## Disadvantages
61+
62+
This gets a bit wordy. Getting it wrong repeatedly will make you hate the
63+
borrow checker. The compiler may fail to optimize away the double store,
64+
resulting in reduced performance as opposed to what you'd do in unsafe
65+
languages.
66+
67+
## Discussion
68+
69+
This pattern is only of interest in Rust. In GC'd languages, you'd take the
70+
reference to the value by default (and the GC would keep track of refs), and in
71+
other low-level languages like C you'd simply alias the pointer and fix things
72+
later.
73+
74+
However, in Rust, we have to do a little more work to do this. An owned value
75+
may only have one owner, so to take it out, we need to put something back in –
76+
like Indiana Jones, replacing the artifact with a bag of sand.
77+
78+
## See also
79+
80+
This gets rid of the [Clone to satisfy the borrow checker] antipattern in a
81+
specific case.
82+
83+
[Clone to satisfy the borrow checker](TODO: Hinges on PR #23)

0 commit comments

Comments
 (0)