diff --git a/text/0000-mem-replace-with.md b/text/0000-mem-replace-with.md new file mode 100644 index 00000000000..00e962b2689 --- /dev/null +++ b/text/0000-mem-replace-with.md @@ -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, +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. + +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"); + } +} + +/// 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(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?