diff --git a/src/libcore/mem.rs b/src/libcore/mem.rs index 9c61f76ac8895..5de9f8859a9e9 100644 --- a/src/libcore/mem.rs +++ b/src/libcore/mem.rs @@ -415,6 +415,71 @@ pub fn replace(dest: &mut T, mut src: T) -> T { src } +/// 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 run, 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. +/// +/// # Example +/// +/// ``` +/// use std::mem; +/// +/// // Dump some stuff into a box. +/// let mut bx: Box = Box::new(200); +/// +/// // Temporarily steal ownership. +/// mem::replace_with(&mut bx, |mut owned| { +/// owner = 5; +/// +/// // The returned value is placed back in `&mut bx`. +/// Box::new(owner) +/// }); +/// ``` +#[inline] +#[unstable(feature = "replace_with", issue = "...")] +pub fn replace_with(val: &mut T, closure: 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); +} + /// Disposes of a value. /// /// While this does call the argument's implementation of `Drop`, it will not diff --git a/src/libcoretest/mem.rs b/src/libcoretest/mem.rs index 01bafe49a7acd..6f86faf3400d9 100644 --- a/src/libcoretest/mem.rs +++ b/src/libcoretest/mem.rs @@ -99,6 +99,31 @@ fn test_replace() { assert!(y.is_some()); } +#[test] +fn test_replace_with() { + let mut x = Some("test".to_string()); + replace_with(&mut x, |_| None); + assert!(x.is_none()); +} + +#[test] +fn test_replace_with_2() { + let mut x = Box::new(21); + replace_with(&mut x, |x| Box::new(x + 5)); + assert_eq!(*x, 26); +} + +#[test] +fn test_replace_with_3() { + let is_called = Cell::new(false); + let mut x = 2; + replace_with(&mut x, |_| { + is_called.set(true); + 3 + }); + assert!(is_called.get()); +} + #[test] fn test_transmute_copy() { assert_eq!(1, unsafe { transmute_copy(&1) });