Skip to content

Instantly share code, notes, and snippets.

@DutchGhost
Created January 4, 2019 19:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DutchGhost/f7c59eb9d0de0599b7f7c75a7fa5884a to your computer and use it in GitHub Desktop.
Save DutchGhost/f7c59eb9d0de0599b7f7c75a7fa5884a to your computer and use it in GitHub Desktop.
use std::cell::RefCell;
trait Defer {
fn call(self: Box<Self>);
}
impl<F: FnOnce(T), T> Defer for (F, T) {
fn call(self: Box<Self>) {
(self.0)(self.1);
}
}
#[derive(Default)]
pub struct List<'a> {
inner: RefCell<Vec<Box<dyn Defer + 'a>>>,
}
unsafe fn transmute_lifetimes_mut<'a, 'b, T: ?Sized>(x: &'a mut T) -> &'b mut T {
std::mem::transmute(x)
}
impl<'a> List<'a> {
fn new() -> Self {
Self {
inner: RefCell::new(Vec::new()),
}
}
fn push<T: 'a>(&self, item: T, closure: impl FnOnce(T) + 'a) -> &'a mut T {
let mut def = Box::new((closure, item));
let ret = unsafe { transmute_lifetimes_mut(&mut def.1) };
self.inner.borrow_mut().push(def);
ret
}
fn execute(mut self) {
let v = std::mem::replace(self.inner.get_mut(), vec![]);
for d in v {
d.call();
}
}
}
/// A guard is a handle to schedule callbacks on, from an outer scope.
#[derive(Default)]
pub struct Guard<'a> {
/// Callbacks to be run on a scope's success.
on_scope_success: List<'a>,
/// Callbacks to be run on a scope's failure.
on_scope_failure: List<'a>,
/// Callbacks to be run on a scope's exit.
on_scope_exit: List<'a>,
}
impl<'a> Guard<'a> {
/// Schedules a closure to run on a scope's success.
pub fn on_scope_success<T: 'a>(&self, item: T, closure: impl FnOnce(T) + 'a) -> &mut T {
self.on_scope_success.push(item, closure)
}
/// Schedules a closure to run on a scope's exit.
pub fn on_scope_exit<T: 'a>(&self, item: T, closure: impl FnOnce(T) + 'a) -> &mut T {
self.on_scope_exit.push(item, closure)
}
/// Schedules a closure to run on a scope's failure.
pub fn on_scope_failure<T: 'a>(&self, item: T, closure: impl FnOnce(T) + 'a) -> &mut T {
self.on_scope_failure.push(item, closure)
}
}
/// A trait to annotate whether a type is `success` or `failure`.
pub trait RetTrait {
fn is_error(&self) -> bool;
}
impl<T, E> RetTrait for Result<T, E> {
/// `Ok(T)` is success, `Err(E)` is failure.
fn is_error(&self) -> bool {
self.is_err()
}
}
impl<T> RetTrait for Option<T> {
/// `Some(T)` is success, `None` is failure.
fn is_error(&self) -> bool {
self.is_none()
}
}
/// Executes the scope `scope`.
/// A scope is a closure, in which access to a guard is granted.
/// A guard is used to schedule callbacks to run on a scope's success, failure, or exit, using
/// [`Guard::on_scope_success`], [`Guard::on_scope_failure`], [`Guard::on_scope_exit`].
///
/// Its important to note that callbacks schedules with [`Guard::on_scope_exit`] will *always* run, and will always run last.
///
/// # Examples
/// ```
/// use scoped::{Guard, scoped};
///
/// fn main() {
/// use std::cell::Cell;
///
/// let mut number = Cell::new(0);
///
/// scoped(|guard| -> Result<(), ()> {
///
/// guard.on_scope_exit(&number, move |n| {
/// assert_eq!(n.get(), 2);
/// n.set(3);
/// });
///
/// guard.on_scope_success(&number, move |n| {
/// assert_eq!(n.get(), 1);
/// n.set(2);
/// });
///
/// number.set(1);
/// Ok(())
/// });
/// assert_eq!(number.get(), 3);
/// }
/// ```
pub fn scoped<'a, R: RetTrait>(scope: impl FnOnce(&mut Guard<'a>) -> R + 'a) -> R {
let mut guard = Guard::default();
let ret = scope(&mut guard);
if !ret.is_error() {
guard.on_scope_success.execute();
} else {
guard.on_scope_failure.execute();
}
guard.on_scope_exit.execute();
ret
}
pub type ScopeResult<E> = Result<(), E>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_list() {
let scope = scoped(|guard| {
let v = guard.on_scope_success(vec![1, 2, 3, 4, 5], |mut v| {
println!("SUCCES!");
assert_eq!(v, vec![1, 2, 3, 4, 5, 6, 7]);
v.push(10);
});
let boxed = guard.on_scope_exit(Box::new(1), move |boxed| {
assert_eq!(*boxed, 12);
});
v.push(6);
v.push(7);
**boxed = 12;
Some(10)
});
}
#[test]
fn main_test() {
use std::cell::Cell;
let number = Cell::new(0);
let n = scoped(|guard| {
let n = Some(1);
guard.on_scope_success(&number, move |b| {
assert!(2 == b.get());
b.set(0);
});
guard.on_scope_failure(&number, move |b| {
b.set(-1);
});
guard.on_scope_exit(&number, move |b| {
b.set(0);
});
number.set(2);
n
});
assert!(number.get() == 0);
assert_eq!(n, Some(1));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment