take_mut/
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
33use std;
34use std::panic;
35use std::cell::Cell;
36use 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.
39pub struct Scope<'s> {
40    active_holes: Cell<usize>,
41    marker: PhantomData<Cell<&'s mut ()>>
42}
43
44impl<'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>() -> 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.
84pub 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]
106pub struct Hole<'c, 'm, T: 'm, F: FnOnce() -> T> {
107    active_holes: &'c Cell<usize>,
108    hole: *mut T,
109    phantom: PhantomData<&'m mut T>,
110    recovery: Option<F>,
111}
112
113impl<'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
128impl<'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]
142fn 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]
163fn 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}