|
| 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