-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: core::mem::replace_with
for temporarily moving out of ownership.
#1736
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
- Feature Name: mem-replace-with | ||
- Start Date: 2016-09-01 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Add a new function to `core::mem` (and thus `std::mem`), `replace_with`, which | ||
invokes a closure that takes temporary ownership over some value behind a | ||
mutable reference. | ||
|
||
This is similar to the functions provided by [the take_mut crate](https://crates.io/crates/take_mut). | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
A common pattern, especially for low-level programmers, is to acquire ownership | ||
of a value behind a reference without immediately giving it a replacement value. | ||
|
||
`core::mem::replace_with` allows such thing by generalizing | ||
`core::mem::replace`, to allow the replacement value being generated by a | ||
closure and even depend on the old value. | ||
|
||
Unfortunately, there are no safe implementation in the standard library, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/are/is/ to match "implementation". Or alternatively, "the standard library doesn't provide any safe implementation" |
||
leading some Rust users to either reimplement a safe interface for such a | ||
feature (often flawed due to unwinding, see below) or use things like | ||
`ptr::read`. | ||
|
||
This should standardize the ecosystem with respect to that. | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
A complete implementation can be found [here](https://github.com/rust-lang/rust/pull/36186). This implementation is pretty simple, with only one trick which is crucial to safety: handling unwinding. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we just include the code here? The implementation is short, and including makes the RFC self-contained. |
||
|
||
Without handling unwinding, the closure could unwind and invoke destructors on | ||
the invalid value. Thus we use "exit guards" which are placed in the stack | ||
frame (as a variable) and holds a destructor exiting the program. | ||
|
||
This behavior is simply specified to match the one of panicking inside | ||
unwinding destructors. This has the nice property of allowing error messages in | ||
the libcore implementation. | ||
|
||
## Implementation | ||
|
||
```rust | ||
/// A guarding type which will abort upon drop. | ||
/// | ||
/// This is used for catching unwinding and transforming it into abort. | ||
/// | ||
/// The destructor should never be called naturally (use `mem::forget()`), and only when unwinding. | ||
struct ExitGuard; | ||
|
||
impl Drop for ExitGuard { | ||
fn drop(&mut self) { | ||
// To avoid unwinding, we abort (we panic, which is equivalent to abort inside an unwinding | ||
// destructor) the program, which ensures that the destructor of the invalidated value | ||
// isn't runned, since this destructor ought to be called only if unwinding happens. | ||
panic!("`replace_with` closure unwinded. For safety reasons, this will \ | ||
abort your program. Check the documentation"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unwinded -> unwound There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also |
||
} | ||
} | ||
|
||
/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value | ||
/// based on the old one. | ||
/// | ||
/// We move out of reference temporarily, to apply a closure, returning a new value, which is then | ||
/// placed at the original value's location. | ||
/// | ||
/// # An important note | ||
/// | ||
/// The behavior on panic (or to be more precise, unwinding) is specified to match the behavior of | ||
/// panicking inside a destructor, which itself is simply specified to not unwind. | ||
#[inline] | ||
#[unstable(feature = "replace_with", issue = "...")] | ||
pub fn replace_with<T, F>(val: &mut T, replace: F) | ||
where F: FnOnce(T) -> T { | ||
// Guard against unwinding. Note that this is critical to safety, to avoid the value behind the | ||
// reference `val` is not dropped twice during unwinding. | ||
let guard = ExitGuard; | ||
|
||
unsafe { | ||
// Take out the value behind the pointer. | ||
let old = ptr::read(val); | ||
// Run the closure. | ||
let new = closure(old); | ||
// Put the result back. | ||
ptr::write(val, new); | ||
} | ||
|
||
// Forget the guard, to avoid panicking. | ||
mem::forget(guard); | ||
} | ||
``` | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
It expands the core library slightly, and the panic semantics might not be | ||
clear for everyone. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
1. Specify the unwinding behavior to abort. | ||
|
||
2. Use `catch_unwind` and print an error on unwinding (in libstd). | ||
|
||
4. Leave it out of the standard library. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
Are there currently any crates relying on invariants that this will break? |
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.
Can you include a simple example of this pattern and the problem it causes?