Skip to content

Commit b8060ab

Browse files
authored
Merge pull request #7 from Sgeo/scoped
Merge into trunk
2 parents 90b25b2 + dbdd82c commit b8060ab

File tree

3 files changed

+184
-90
lines changed

3 files changed

+184
-90
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "take_mut"
3-
version = "0.1.3"
3+
version = "0.2.0"
44
authors = ["Sgeo <[email protected]>"]
55
license = "MIT"
66
homepage = "https://github.com/Sgeo/take_mut"

src/lib.rs

+8-89
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
//! This crate provides (at this time) a single function, `take()`.
1+
//! This crate provides several functions for handling `&mut T` including `take()`.
22
//!
33
//! `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`.
44
//!
5-
//! 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`.
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`.
66
//! Use `take_or_recover()` to replace the `&mut T` with a recovery value before continuing the panic.
77
//!
88
//! 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`.
99
1010
use std::panic;
1111

12+
pub mod scoped;
13+
1214
/// Allows use of a value pointed to by `&mut T` as though it was owned, as long as a `T` is made available afterwards.
1315
///
1416
/// The closure must return a valid T.
1517
/// # Important
16-
/// Will exit the program (with status code 101) if the closure panics.
18+
/// Will abort the program if the closure panics.
1719
///
1820
/// # Example
1921
/// ```
@@ -33,7 +35,7 @@ pub fn take<T, F>(mut_ref: &mut T, closure: F)
3335
unsafe {
3436
let old_t = ptr::read(mut_ref);
3537
let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t)))
36-
.unwrap_or_else(|_| ::std::process::exit(101));
38+
.unwrap_or_else(|_| ::std::process::abort());
3739
ptr::write(mut_ref, new_t);
3840
}
3941
}
@@ -85,7 +87,7 @@ pub fn take_or_recover<T, F, R>(mut_ref: &mut T, recover: R, closure: F)
8587
match new_t {
8688
Err(err) => {
8789
let r = panic::catch_unwind(panic::AssertUnwindSafe(|| recover()))
88-
.unwrap_or_else(|_| ::std::process::exit(101));
90+
.unwrap_or_else(|_| ::std::process::abort());
8991
ptr::write(mut_ref, r);
9092
panic::resume_unwind(err);
9193
}
@@ -95,70 +97,6 @@ pub fn take_or_recover<T, F, R>(mut_ref: &mut T, recover: R, closure: F)
9597
}
9698

9799

98-
use std::rc::Rc;
99-
use std::marker::PhantomData;
100-
101-
pub struct Scope<'s> {
102-
active_holes: Rc<()>,
103-
marker: PhantomData<&'s mut ()>
104-
}
105-
106-
impl<'s> Scope<'s> {
107-
108-
// Guarantees break if this is &self instead of &mut self, and I don't know why
109-
// Reason to use Rcs is because can't return a & from a &mut self nicely
110-
pub fn take<'m: 's, T: 'm>(&mut self, mut_ref: &'m mut T) -> (T, Hole<'m, T>) {
111-
use std::ptr;
112-
113-
let t: T;
114-
let hole: Hole<'m, T>;
115-
unsafe {
116-
t = ptr::read(mut_ref);
117-
hole = Hole { active_holes: Some(self.active_holes.clone()), hole: mut_ref };
118-
};
119-
(t, hole)
120-
}
121-
}
122-
123-
pub fn scope<'s, F, R>(f: F) -> R
124-
where F: FnOnce(&mut Scope<'s>) -> R {
125-
exit_on_panic(|| {
126-
let mut this = Scope { active_holes: Rc::new(()), marker: PhantomData };
127-
let r = f(&mut this);
128-
if Rc::strong_count(&this.active_holes) != 1 {
129-
panic!("There are still unfilled Holes at the end of the scope!");
130-
}
131-
r
132-
})
133-
}
134-
135-
#[must_use]
136-
pub struct Hole<'m, T: 'm> {
137-
active_holes: Option<Rc<()>>,
138-
hole: &'m mut T
139-
}
140-
141-
impl<'m, T: 'm> Hole<'m, T> {
142-
pub fn fill(mut self, t: T) {
143-
use std::ptr;
144-
use std::mem;
145-
146-
unsafe {
147-
ptr::write(self.hole, t);
148-
}
149-
self.active_holes.take();
150-
mem::forget(self);
151-
}
152-
}
153-
154-
impl<'m, T: 'm> Drop for Hole<'m, T> {
155-
fn drop(&mut self) {
156-
panic!("An unfilled Hole was destructed!");
157-
}
158-
}
159-
160-
161-
162100

163101

164102
#[test]
@@ -208,24 +146,5 @@ fn it_works_recover_panic() {
208146
assert_eq!(&foo, &Foo::C);
209147
}
210148

211-
#[test]
212-
fn scope_based_take() {
213-
#[derive(Debug)]
214-
struct Foo;
215-
216-
#[derive(Debug)]
217-
struct Bar {
218-
a: Foo,
219-
b: Foo
220-
}
221-
let mut bar = Bar { a: Foo, b: Foo };
222-
scope(|scope| {
223-
let (a, a_hole) = scope.take(&mut bar.a);
224-
let (b, b_hole) = scope.take(&mut bar.b);
225-
// Imagine consuming a and b
226-
a_hole.fill(Foo);
227-
b_hole.fill(Foo);
228-
});
229-
println!("{:?}", &bar);
230-
}
149+
231150

src/scoped.rs

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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<usize>,
41+
marker: PhantomData<Cell<&'s mut ()>>
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>() -> 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<usize>,
108+
hole: *mut T,
109+
phantom: PhantomData<&'m mut T>,
110+
recovery: Option<F>,
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+
}

0 commit comments

Comments
 (0)