Skip to content

Commit

Permalink
Merge pull request #7 from Sgeo/scoped
Browse files Browse the repository at this point in the history
Merge into trunk
  • Loading branch information
Sgeo authored Aug 6, 2017
2 parents 90b25b2 + dbdd82c commit b8060ab
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 90 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "take_mut"
version = "0.1.3"
version = "0.2.0"
authors = ["Sgeo <[email protected]>"]
license = "MIT"
homepage = "https://github.com/Sgeo/take_mut"
Expand Down
97 changes: 8 additions & 89 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
//! This crate provides (at this time) a single function, `take()`.
//! This crate provides several functions for handling `&mut T` including `take()`.
//!
//! `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`.
//!
//! 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`.
//! 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`.
//! Use `take_or_recover()` to replace the `&mut T` with a recovery value before continuing the panic.
//!
//! 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`.
use std::panic;

pub mod scoped;

/// Allows use of a value pointed to by `&mut T` as though it was owned, as long as a `T` is made available afterwards.
///
/// The closure must return a valid T.
/// # Important
/// Will exit the program (with status code 101) if the closure panics.
/// Will abort the program if the closure panics.
///
/// # Example
/// ```
Expand All @@ -33,7 +35,7 @@ pub fn take<T, F>(mut_ref: &mut T, closure: F)
unsafe {
let old_t = ptr::read(mut_ref);
let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t)))
.unwrap_or_else(|_| ::std::process::exit(101));
.unwrap_or_else(|_| ::std::process::abort());
ptr::write(mut_ref, new_t);
}
}
Expand Down Expand Up @@ -85,7 +87,7 @@ pub fn take_or_recover<T, F, R>(mut_ref: &mut T, recover: R, closure: F)
match new_t {
Err(err) => {
let r = panic::catch_unwind(panic::AssertUnwindSafe(|| recover()))
.unwrap_or_else(|_| ::std::process::exit(101));
.unwrap_or_else(|_| ::std::process::abort());
ptr::write(mut_ref, r);
panic::resume_unwind(err);
}
Expand All @@ -95,70 +97,6 @@ pub fn take_or_recover<T, F, R>(mut_ref: &mut T, recover: R, closure: F)
}


use std::rc::Rc;
use std::marker::PhantomData;

pub struct Scope<'s> {
active_holes: Rc<()>,
marker: PhantomData<&'s mut ()>
}

impl<'s> Scope<'s> {

// Guarantees break if this is &self instead of &mut self, and I don't know why
// Reason to use Rcs is because can't return a & from a &mut self nicely
pub fn take<'m: 's, T: 'm>(&mut self, mut_ref: &'m mut T) -> (T, Hole<'m, T>) {
use std::ptr;

let t: T;
let hole: Hole<'m, T>;
unsafe {
t = ptr::read(mut_ref);
hole = Hole { active_holes: Some(self.active_holes.clone()), hole: mut_ref };
};
(t, hole)
}
}

pub fn scope<'s, F, R>(f: F) -> R
where F: FnOnce(&mut Scope<'s>) -> R {
exit_on_panic(|| {
let mut this = Scope { active_holes: Rc::new(()), marker: PhantomData };
let r = f(&mut this);
if Rc::strong_count(&this.active_holes) != 1 {
panic!("There are still unfilled Holes at the end of the scope!");
}
r
})
}

#[must_use]
pub struct Hole<'m, T: 'm> {
active_holes: Option<Rc<()>>,
hole: &'m mut T
}

impl<'m, T: 'm> Hole<'m, T> {
pub fn fill(mut self, t: T) {
use std::ptr;
use std::mem;

unsafe {
ptr::write(self.hole, t);
}
self.active_holes.take();
mem::forget(self);
}
}

impl<'m, T: 'm> Drop for Hole<'m, T> {
fn drop(&mut self) {
panic!("An unfilled Hole was destructed!");
}
}





#[test]
Expand Down Expand Up @@ -208,24 +146,5 @@ fn it_works_recover_panic() {
assert_eq!(&foo, &Foo::C);
}

#[test]
fn scope_based_take() {
#[derive(Debug)]
struct Foo;

#[derive(Debug)]
struct Bar {
a: Foo,
b: Foo
}
let mut bar = Bar { a: Foo, b: Foo };
scope(|scope| {
let (a, a_hole) = scope.take(&mut bar.a);
let (b, b_hole) = scope.take(&mut bar.b);
// Imagine consuming a and b
a_hole.fill(Foo);
b_hole.fill(Foo);
});
println!("{:?}", &bar);
}


175 changes: 175 additions & 0 deletions src/scoped.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//! This module provides a scoped API, allowing for taking an arbitrary number of `&mut T` into `T` within one closure.
//! The references are all required to outlive the closure.
//!
//! # Example
//! ```
//! use take_mut::scoped;
//! struct Foo;
//! let mut foo = Foo; // Must outlive scope
//! scoped::scope(|scope| {
//! let (t, hole) = scope.take(&mut foo);
//! drop(t);
//! hole.fill(Foo); // If not called before the closure ends, causes an abort.
//! });
//! ```
//!
//! # Invalid Example (does not compile)
//! ```ignore
//! use take_mut::scoped;
//! struct Foo;
//! scoped::scope(|scope| {
//! let mut foo = Foo; // Invalid because foo must come from outside the scope.
//! let (t, hole) = scope.take(&mut foo);
//! drop(t);
//! hole.fill(Foo);
//! });
//! ```
//!
//! `Scope` also offers `take_or_recover`, which takes a function to call in the event the hole isn't filled.
#![warn(missing_docs)]


use std;
use std::panic;
use std::cell::Cell;
use std::marker::PhantomData;

/// 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.
pub struct Scope<'s> {
active_holes: Cell<usize>,
marker: PhantomData<Cell<&'s mut ()>>
}

impl<'s> Scope<'s> {

/// Takes a `(T, Hole<'c, 'm, T, F>)` from an `&'m mut T`.
///
/// 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.
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>) {
use std::ptr;

let t: T;
let hole: Hole<'c, 'm, T, F>;
let num_of_holes = self.active_holes.get();
if num_of_holes == std::usize::MAX {
panic!("Too many holes!");
}
self.active_holes.set(num_of_holes + 1);
unsafe {
t = ptr::read(mut_ref as *mut T);
hole = Hole {
active_holes: &self.active_holes,
hole: mut_ref as *mut T,
phantom: PhantomData,
recovery: Some(recovery)
};
};
(t, hole)
}

/// Takes a `(T, Hole<'c, 'm, T, F>)` from an `&'m mut T`.
pub fn take<'c, 'm: 's, T: 'm>(&'c self, mut_ref: &'m mut T) -> (T, Hole<'c, 'm, T, fn() -> T>) {
#[allow(missing_docs)]
fn panic<T>() -> T {
panic!("Failed to recover a Hole!")
}
self.take_or_recover(mut_ref, panic)
}
}

/// Main function to create a `Scope`.
///
/// If the given closure ends without all Holes filled, will abort the program.
pub fn scope<'s, F, R>(f: F) -> R
where F: FnOnce(&Scope<'s>) -> R {
let this = Scope { active_holes: Cell::new(0), marker: PhantomData };
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
f(&this)
}));
if this.active_holes.get() != 0 {
std::process::abort();
}
match result {
Ok(r) => r,
Err(p) => panic::resume_unwind(p),
}

}

/// 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`.
///
/// An unfilled `Hole<'c, 'm, T, F> that is destructed will try to use `F` to fill the hole.
///
/// If the scope ends without the `Hole` being filled, the program will `std::process::abort()`.
#[must_use]
pub struct Hole<'c, 'm, T: 'm, F: FnOnce() -> T> {
active_holes: &'c Cell<usize>,
hole: *mut T,
phantom: PhantomData<&'m mut T>,
recovery: Option<F>,
}

impl<'c, 'm, T: 'm, F: FnOnce() -> T> Hole<'c, 'm, T, F> {
/// Fills the Hole.
pub fn fill(self, t: T) {
use std::ptr;
use std::mem;

unsafe {
ptr::write(self.hole, t);
}
let num_holes = self.active_holes.get();
self.active_holes.set(num_holes - 1);
mem::forget(self);
}
}

impl<'c, 'm, T: 'm, F: FnOnce() -> T> Drop for Hole<'c, 'm, T, F> {
fn drop(&mut self) {
use std::ptr;

let t = (self.recovery.take().expect("No recovery function in Hole!"))();
unsafe {
ptr::write(self.hole, t);
}
let num_holes = self.active_holes.get();
self.active_holes.set(num_holes - 1);
}
}

#[test]
fn scope_based_take() {
#[derive(Debug)]
struct Foo;

#[derive(Debug)]
struct Bar {
a: Foo,
b: Foo
}
let mut bar = Bar { a: Foo, b: Foo };
scope(|scope| {
let (a, a_hole) = scope.take(&mut bar.a);
let (b, b_hole) = scope.take(&mut bar.b);
// Imagine consuming a and b
a_hole.fill(Foo);
b_hole.fill(Foo);
});
println!("{:?}", &bar);
}

#[test]
fn panic_on_recovered_panic() {
use std::panic;

struct Foo;
let mut foo = Foo;
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
scope(|scope| {
let (t, hole) = scope.take_or_recover(&mut foo, || Foo);
panic!("Oops!");
});
}));
assert!(result.is_err());
}

0 comments on commit b8060ab

Please sign in to comment.