├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── lib.rs └── scoped.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "take_mut" 3 | version = "0.2.2" 4 | authors = ["Sgeo "] 5 | license = "MIT" 6 | homepage = "https://github.com/Sgeo/take_mut" 7 | repository = "https://github.com/Sgeo/take_mut" 8 | description = "Take a T from a &mut T temporarily" 9 | categories = ["rust-patterns"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sgeo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # take_mut 2 | 3 | This crate provides (at this time) a single function, `take()`. 4 | 5 | `take()` allows for taking `T` out of a `&mut T`, doing anything with it including consuming it, and producing another `T` to put back in the `&mut T`. 6 | 7 | During `take()`, if a panic occurs, the entire process will be exited, as there's no valid `T` to put back into the `&mut T`. 8 | 9 | Contrast with `std::mem::replace()`, which allows for putting a different `T` into a `&mut T`, but requiring the new `T` to be available before being able to consume the old `T`. 10 | 11 | # Example 12 | ```rust 13 | struct Foo; 14 | let mut foo = Foo; 15 | take_mut::take(&mut foo, |foo| { 16 | // Can now consume the Foo, and provide a new value later 17 | drop(foo); 18 | // Do more stuff 19 | Foo // Return new Foo from closure, which goes back into the &mut Foo 20 | }); 21 | ``` -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides several functions for handling `&mut T` including `take()`. 2 | //! 3 | //! `take()` allows for taking `T` out of a `&mut T`, doing anything with it including consuming it, and producing another `T` to put back in the `&mut T`. 4 | //! 5 | //! During `take()`, if a panic occurs, the entire process will be aborted, as there's no valid `T` to put back into the `&mut T`. 6 | //! Use `take_or_recover()` to replace the `&mut T` with a recovery value before continuing the panic. 7 | //! 8 | //! Contrast with `std::mem::replace()`, which allows for putting a different `T` into a `&mut T`, but requiring the new `T` to be available before being able to consume the old `T`. 9 | 10 | use std::panic; 11 | 12 | pub mod scoped; 13 | 14 | /// Allows use of a value pointed to by `&mut T` as though it was owned, as long as a `T` is made available afterwards. 15 | /// 16 | /// The closure must return a valid T. 17 | /// # Important 18 | /// Will abort the program if the closure panics. 19 | /// 20 | /// # Example 21 | /// ``` 22 | /// struct Foo; 23 | /// let mut foo = Foo; 24 | /// take_mut::take(&mut foo, |foo| { 25 | /// // Can now consume the Foo, and provide a new value later 26 | /// drop(foo); 27 | /// // Do more stuff 28 | /// Foo // Return new Foo from closure, which goes back into the &mut Foo 29 | /// }); 30 | /// ``` 31 | pub fn take(mut_ref: &mut T, closure: F) 32 | where F: FnOnce(T) -> T { 33 | use std::ptr; 34 | 35 | unsafe { 36 | let old_t = ptr::read(mut_ref); 37 | let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t))) 38 | .unwrap_or_else(|_| ::std::process::abort()); 39 | ptr::write(mut_ref, new_t); 40 | } 41 | } 42 | 43 | #[test] 44 | fn it_works() { 45 | #[derive(PartialEq, Eq, Debug)] 46 | enum Foo {A, B}; 47 | impl Drop for Foo { 48 | fn drop(&mut self) { 49 | match *self { 50 | Foo::A => println!("Foo::A dropped"), 51 | Foo::B => println!("Foo::B dropped") 52 | } 53 | } 54 | } 55 | let mut foo = Foo::A; 56 | take(&mut foo, |f| { 57 | drop(f); 58 | Foo::B 59 | }); 60 | assert_eq!(&foo, &Foo::B); 61 | } 62 | 63 | 64 | /// Allows use of a value pointed to by `&mut T` as though it was owned, as long as a `T` is made available afterwards. 65 | /// 66 | /// The closure must return a valid T. 67 | /// # Important 68 | /// Will replace `&mut T` with `recover` if the closure panics, then continues the panic. 69 | /// 70 | /// # Example 71 | /// ``` 72 | /// struct Foo; 73 | /// let mut foo = Foo; 74 | /// take_mut::take_or_recover(&mut foo, || Foo, |foo| { 75 | /// // Can now consume the Foo, and provide a new value later 76 | /// drop(foo); 77 | /// // Do more stuff 78 | /// Foo // Return new Foo from closure, which goes back into the &mut Foo 79 | /// }); 80 | /// ``` 81 | pub fn take_or_recover(mut_ref: &mut T, recover: R, closure: F) 82 | where F: FnOnce(T) -> T, R: FnOnce() -> T { 83 | use std::ptr; 84 | unsafe { 85 | let old_t = ptr::read(mut_ref); 86 | let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t))); 87 | match new_t { 88 | Err(err) => { 89 | let r = panic::catch_unwind(panic::AssertUnwindSafe(|| recover())) 90 | .unwrap_or_else(|_| ::std::process::abort()); 91 | ptr::write(mut_ref, r); 92 | panic::resume_unwind(err); 93 | } 94 | Ok(new_t) => ptr::write(mut_ref, new_t), 95 | } 96 | } 97 | } 98 | 99 | 100 | 101 | 102 | #[test] 103 | fn it_works_recover() { 104 | #[derive(PartialEq, Eq, Debug)] 105 | enum Foo {A, B}; 106 | impl Drop for Foo { 107 | fn drop(&mut self) { 108 | match *self { 109 | Foo::A => println!("Foo::A dropped"), 110 | Foo::B => println!("Foo::B dropped") 111 | } 112 | } 113 | } 114 | let mut foo = Foo::A; 115 | take_or_recover(&mut foo, || Foo::A, |f| { 116 | drop(f); 117 | Foo::B 118 | }); 119 | assert_eq!(&foo, &Foo::B); 120 | } 121 | 122 | #[test] 123 | fn it_works_recover_panic() { 124 | #[derive(PartialEq, Eq, Debug)] 125 | enum Foo {A, B, C}; 126 | impl Drop for Foo { 127 | fn drop(&mut self) { 128 | match *self { 129 | Foo::A => println!("Foo::A dropped"), 130 | Foo::B => println!("Foo::B dropped"), 131 | Foo::C => println!("Foo::C dropped") 132 | } 133 | } 134 | } 135 | let mut foo = Foo::A; 136 | 137 | let res = panic::catch_unwind(panic::AssertUnwindSafe(|| { 138 | take_or_recover(&mut foo, || Foo::C, |f| { 139 | drop(f); 140 | panic!("panic"); 141 | Foo::B 142 | }); 143 | })); 144 | 145 | assert!(res.is_err()); 146 | assert_eq!(&foo, &Foo::C); 147 | } 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/scoped.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a scoped API, allowing for taking an arbitrary number of `&mut T` into `T` within one closure. 2 | //! The references are all required to outlive the closure. 3 | //! 4 | //! # Example 5 | //! ``` 6 | //! use take_mut::scoped; 7 | //! struct Foo; 8 | //! let mut foo = Foo; // Must outlive scope 9 | //! scoped::scope(|scope| { 10 | //! let (t, hole) = scope.take(&mut foo); 11 | //! drop(t); 12 | //! hole.fill(Foo); // If not called before the closure ends, causes an abort. 13 | //! }); 14 | //! ``` 15 | //! 16 | //! # Invalid Example (does not compile) 17 | //! ```ignore 18 | //! use take_mut::scoped; 19 | //! struct Foo; 20 | //! scoped::scope(|scope| { 21 | //! let mut foo = Foo; // Invalid because foo must come from outside the scope. 22 | //! let (t, hole) = scope.take(&mut foo); 23 | //! drop(t); 24 | //! hole.fill(Foo); 25 | //! }); 26 | //! ``` 27 | //! 28 | //! `Scope` also offers `take_or_recover`, which takes a function to call in the event the hole isn't filled. 29 | 30 | #![warn(missing_docs)] 31 | 32 | 33 | use std; 34 | use std::panic; 35 | use std::cell::Cell; 36 | use std::marker::PhantomData; 37 | 38 | /// Represents a scope within which, it is possible to take a `T` from a `&mut T` as long as the `&mut T` outlives the scope. 39 | pub struct Scope<'s> { 40 | active_holes: Cell, 41 | marker: PhantomData> 42 | } 43 | 44 | impl<'s> Scope<'s> { 45 | 46 | /// Takes a `(T, Hole<'c, 'm, T, F>)` from an `&'m mut T`. 47 | /// 48 | /// If the `Hole` is dropped without being filled, either due to panic or forgetting to fill, will run the `recovery` function to obtain a `T` to fill itself with. 49 | pub fn take_or_recover<'c, 'm: 's, T: 'm, F: FnOnce() -> T>(&'c self, mut_ref: &'m mut T, recovery: F) -> (T, Hole<'c, 'm, T, F>) { 50 | use std::ptr; 51 | 52 | let t: T; 53 | let hole: Hole<'c, 'm, T, F>; 54 | let num_of_holes = self.active_holes.get(); 55 | if num_of_holes == std::usize::MAX { 56 | panic!("Too many holes!"); 57 | } 58 | self.active_holes.set(num_of_holes + 1); 59 | unsafe { 60 | t = ptr::read(mut_ref as *mut T); 61 | hole = Hole { 62 | active_holes: &self.active_holes, 63 | hole: mut_ref as *mut T, 64 | phantom: PhantomData, 65 | recovery: Some(recovery) 66 | }; 67 | }; 68 | (t, hole) 69 | } 70 | 71 | /// Takes a `(T, Hole<'c, 'm, T, F>)` from an `&'m mut T`. 72 | pub fn take<'c, 'm: 's, T: 'm>(&'c self, mut_ref: &'m mut T) -> (T, Hole<'c, 'm, T, fn() -> T>) { 73 | #[allow(missing_docs)] 74 | fn panic() -> T { 75 | panic!("Failed to recover a Hole!") 76 | } 77 | self.take_or_recover(mut_ref, panic) 78 | } 79 | } 80 | 81 | /// Main function to create a `Scope`. 82 | /// 83 | /// If the given closure ends without all Holes filled, will abort the program. 84 | pub fn scope<'s, F, R>(f: F) -> R 85 | where F: FnOnce(&Scope<'s>) -> R { 86 | let this = Scope { active_holes: Cell::new(0), marker: PhantomData }; 87 | let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { 88 | f(&this) 89 | })); 90 | if this.active_holes.get() != 0 { 91 | std::process::abort(); 92 | } 93 | match result { 94 | Ok(r) => r, 95 | Err(p) => panic::resume_unwind(p), 96 | } 97 | 98 | } 99 | 100 | /// A `Hole<'c, 'm, T, F>` represents an unfilled `&'m mut T` which must be filled before the end of the `Scope` with lifetime `'c` and recovery closure `F`. 101 | /// 102 | /// An unfilled `Hole<'c, 'm, T, F> that is destructed will try to use `F` to fill the hole. 103 | /// 104 | /// If the scope ends without the `Hole` being filled, the program will `std::process::abort()`. 105 | #[must_use] 106 | pub struct Hole<'c, 'm, T: 'm, F: FnOnce() -> T> { 107 | active_holes: &'c Cell, 108 | hole: *mut T, 109 | phantom: PhantomData<&'m mut T>, 110 | recovery: Option, 111 | } 112 | 113 | impl<'c, 'm, T: 'm, F: FnOnce() -> T> Hole<'c, 'm, T, F> { 114 | /// Fills the Hole. 115 | pub fn fill(self, t: T) { 116 | use std::ptr; 117 | use std::mem; 118 | 119 | unsafe { 120 | ptr::write(self.hole, t); 121 | } 122 | let num_holes = self.active_holes.get(); 123 | self.active_holes.set(num_holes - 1); 124 | mem::forget(self); 125 | } 126 | } 127 | 128 | impl<'c, 'm, T: 'm, F: FnOnce() -> T> Drop for Hole<'c, 'm, T, F> { 129 | fn drop(&mut self) { 130 | use std::ptr; 131 | 132 | let t = (self.recovery.take().expect("No recovery function in Hole!"))(); 133 | unsafe { 134 | ptr::write(self.hole, t); 135 | } 136 | let num_holes = self.active_holes.get(); 137 | self.active_holes.set(num_holes - 1); 138 | } 139 | } 140 | 141 | #[test] 142 | fn scope_based_take() { 143 | #[derive(Debug)] 144 | struct Foo; 145 | 146 | #[derive(Debug)] 147 | struct Bar { 148 | a: Foo, 149 | b: Foo 150 | } 151 | let mut bar = Bar { a: Foo, b: Foo }; 152 | scope(|scope| { 153 | let (a, a_hole) = scope.take(&mut bar.a); 154 | let (b, b_hole) = scope.take(&mut bar.b); 155 | // Imagine consuming a and b 156 | a_hole.fill(Foo); 157 | b_hole.fill(Foo); 158 | }); 159 | println!("{:?}", &bar); 160 | } 161 | 162 | #[test] 163 | fn panic_on_recovered_panic() { 164 | use std::panic; 165 | 166 | struct Foo; 167 | let mut foo = Foo; 168 | let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { 169 | scope(|scope| { 170 | let (t, hole) = scope.take_or_recover(&mut foo, || Foo); 171 | panic!("Oops!"); 172 | }); 173 | })); 174 | assert!(result.is_err()); 175 | } --------------------------------------------------------------------------------