-
Notifications
You must be signed in to change notification settings - Fork 383
new idiom: mem::replace #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# `mem::replace` to keep owned values in changed enums | ||
|
||
## Description | ||
|
||
Say we have a `&mut MyEnum` which has (at least) two variants, | ||
`A { name: String, x: u8 }` and `B { name: String }`. Now we want to change | ||
`MyEnum::A` to a `B` if `x` is zero, while keeping `MyEnum::B` intact. | ||
|
||
We can do this without cloning the `name`. | ||
|
||
## Example | ||
|
||
```rust | ||
use std::mem; | ||
|
||
fn a_to_b(e: &mut MyEnum) { | ||
// we mutably borrow `e` here. This precludes us from changing it directly | ||
// as in `*e = ...`, because the borrow checker won't allow it. Of course | ||
// we could just take a reference to `name` and clone that, but why pay an | ||
// extra allocation for something we already have? | ||
let have_name = match *e { | ||
MyEnum::A { ref mut name, x } if x == 0 => { | ||
// this takes out our `name` and put in an empty String instead | ||
// note that empty strings don't allocate | ||
Some(mem::replace(name, "".to_string())) | ||
} | ||
// nothing to do in all other cases | ||
_ => None | ||
}; | ||
// the mutable borrow ends here, so we can change `e` | ||
if let Some(name) = have_name { *e = MyEnum::B { name: name } } | ||
} | ||
``` | ||
|
||
|
||
## Motivation | ||
|
||
When working with enums, we may want to change an enum value in place, perhaps | ||
to another variant. This is usually done in two phases to keep the borrow | ||
checker happy. In the first phase, we observe the existing value and look at | ||
its parts to decide what to do next. In the second phase we may conditionally | ||
change the value (as in the example above). | ||
|
||
The borrow checker won't allow us to take out `name` of the enum (because | ||
*something* must be there. We could of course `.clone()` name and put the clone | ||
into our `MyEnum::B`, but that would be an instance of the [Clone to satisfy | ||
the borrow checker] antipattern. Anyway, we can avoid the extra allocation by | ||
changing `e` with only a mutable borrow. | ||
|
||
`mem::replace` lets us swap out the value, replacing it with something else. In | ||
this case, we put in an empty `String`, which does not need to allocate. As a | ||
result, we get the original `name` *as an owned value*. We can wrap this in | ||
an `Option` or another enum that we can destructure in the next step to put the | ||
contained values into our mutably borrowed enum. | ||
|
||
## Advantages | ||
|
||
Look ma, no allocation! Also you may feel like Indiana Jones while doing it. | ||
|
||
## Disadvantages | ||
|
||
This gets a bit wordy. Getting it wrong repeatedly will make you hate the | ||
borrow checker. The compiler may fail to optimize away the double store, | ||
resulting in reduced performance as opposed to what you'd do in unsafe | ||
languages. | ||
|
||
## Discussion | ||
|
||
This pattern is only of interest in Rust. In GC'd languages, you'd take the | ||
reference to the value by default (and the GC would keep track of refs), and in | ||
other low-level languages like C you'd simply alias the pointer and fix things | ||
later. | ||
|
||
However, in Rust, we have to do a little more work to do this. An owned value | ||
may only have one owner, so to take it out, we need to put something back in – | ||
like Indiana Jones, replacing the artifact with a bag of sand. | ||
|
||
## See also | ||
|
||
This gets rid of the [Clone to satisfy the borrow checker] antipattern in a | ||
specific case. | ||
|
||
[Clone to satisfy the borrow checker](TODO: Hinges on PR #23) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still remember the day I learned
mem::replace
, and this is exactly how I felt. 😎There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welcome to the club... 😎