#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[doc(hidden)]
pub use core::{cell::RefCell, mem::{transmute, replace}, marker::PhantomData};
#[doc(hidden)]
pub use alloc::{rc::Rc, vec::Vec};
#[cfg(not(feature = "std"))]
#[macro_export]
mod local_key;
#[doc(hidden)]
#[cfg(not(feature = "std"))]
pub use local_key::LocalKey;
#[doc(hidden)]
#[cfg(feature = "std")]
pub use std::thread::LocalKey;
#[doc(hidden)]
#[cfg(feature = "std")]
#[macro_export]
macro_rules! thread_local_impl {
($(#[$attr:meta])* static $name:ident: $t:ty = $init:expr) => (
thread_local!($(#[$attr])* static $name: $t = $init);
);
}
#[doc(hidden)]
#[cfg(not(feature = "std"))]
#[macro_export]
macro_rules! thread_local_impl {
($(#[$attr:meta])* static $name:ident: $t:ty = $init:expr) => (
$(#[$attr])*
static $name: $crate::LocalKey<$t> = {
fn __init() -> $t { $init }
$crate::local_key_init!(__init)
};
);
}
#[doc(hidden)]
pub type GlobalInner<T> = RefCell<Vec<Rc<RefCell<*mut T>>>>;
type Global<T> = LocalKey<GlobalInner<T>>;
struct PopGlobal<'a, T: 'a + ?Sized> {
global_stack: &'a GlobalInner<T>,
}
impl<'a, T: 'a + ?Sized> Drop for PopGlobal<'a, T> {
fn drop(&mut self) {
self.global_stack.borrow_mut().pop();
}
}
#[doc(hidden)]
pub fn using<T: ?Sized, R, F: FnOnce() -> R>(
global: &'static Global<T>,
protected: &mut T,
f: F,
) -> R {
global.with(|r| {
r.borrow_mut().push(
Rc::new(RefCell::new(protected as _)),
);
let _guard = PopGlobal { global_stack: r };
f()
})
}
#[doc(hidden)]
pub fn using_once<T: ?Sized, R, F: FnOnce() -> R>(
global: &'static Global<T>,
protected: &mut T,
f: F,
) -> R {
global.with(|r| {
if r.borrow().last().is_some() {
f()
} else {
r.borrow_mut().push(
Rc::new(RefCell::new(protected as _)),
);
let _guard = PopGlobal { global_stack: r };
f()
}
})
}
#[doc(hidden)]
pub fn with<T: ?Sized, R, F: FnOnce(&mut T) -> R>(
global: &'static Global<T>,
mutator: F,
) -> Option<R> {
global.with(|r| {
let last = r.borrow().last().cloned();
match last {
Some(ptr) => unsafe {
Some(mutator(&mut **ptr.borrow_mut()))
}
None => None,
}
})
}
#[macro_export]
macro_rules! environmental {
($name:ident : $t:ty) => {
#[allow(non_camel_case_types)]
struct $name { __private_field: () }
$crate::thread_local_impl! {
static GLOBAL: $crate::GlobalInner<$t> = Default::default()
}
impl $name {
#[allow(unused_imports)]
#[allow(dead_code)]
pub fn using<R, F: FnOnce() -> R>(
protected: &mut $t,
f: F,
) -> R {
$crate::using(&GLOBAL, protected, f)
}
#[allow(dead_code)]
pub fn with<R, F: FnOnce(&mut $t) -> R>(
f: F,
) -> Option<R> {
$crate::with(&GLOBAL, |x| f(x))
}
#[allow(dead_code)]
pub fn using_once<R, F: FnOnce() -> R>(
protected: &mut $t,
f: F,
) -> R {
$crate::using_once(&GLOBAL, protected, f)
}
}
};
($name:ident : trait @$t:ident [$($args:ty,)*]) => {
#[allow(non_camel_case_types, dead_code)]
struct $name { __private_field: () }
$crate::thread_local_impl! {
static GLOBAL: $crate::GlobalInner<(dyn $t<$($args),*> + 'static)>
= Default::default()
}
impl $name {
#[allow(unused_imports)]
#[allow(dead_code)]
pub fn using<R, F: FnOnce() -> R>(
protected: &mut dyn $t<$($args),*>,
f: F
) -> R {
let lifetime_extended = unsafe {
$crate::transmute::<&mut dyn $t<$($args),*>, &mut (dyn $t<$($args),*> + 'static)>(protected)
};
$crate::using(&GLOBAL, lifetime_extended, f)
}
#[allow(dead_code)]
pub fn with<R, F: for<'a> FnOnce(&'a mut (dyn $t<$($args),*> + 'a)) -> R>(
f: F
) -> Option<R> {
$crate::with(&GLOBAL, |x| f(x))
}
#[allow(unused_imports)]
#[allow(dead_code)]
pub fn using_once<R, F: FnOnce() -> R>(
protected: &mut dyn $t<$($args),*>,
f: F
) -> R {
let lifetime_extended = unsafe {
$crate::transmute::<&mut dyn $t<$($args),*>, &mut (dyn $t<$($args),*> + 'static)>(protected)
};
$crate::using_once(&GLOBAL, lifetime_extended, f)
}
}
};
($name:ident<$traittype:ident> : trait $t:ident <$concretetype:ty>) => {
#[allow(non_camel_case_types, dead_code)]
struct $name <H: $traittype> { _private_field: $crate::PhantomData<H> }
$crate::thread_local_impl! {
static GLOBAL: $crate::GlobalInner<(dyn $t<$concretetype> + 'static)>
= Default::default()
}
impl<H: $traittype> $name<H> {
#[allow(unused_imports)]
#[allow(dead_code)]
pub fn using<R, F: FnOnce() -> R>(
protected: &mut dyn $t<H>,
f: F
) -> R {
let lifetime_extended = unsafe {
$crate::transmute::<&mut dyn $t<H>, &mut (dyn $t<$concretetype> + 'static)>(protected)
};
$crate::using(&GLOBAL, lifetime_extended, f)
}
#[allow(dead_code)]
pub fn with<R, F: for<'a> FnOnce(&'a mut (dyn $t<$concretetype> + 'a)) -> R>(
f: F
) -> Option<R> {
$crate::with(&GLOBAL, |x| f(x))
}
#[allow(unused_imports)]
#[allow(dead_code)]
pub fn using_once<R, F: FnOnce() -> R>(
protected: &mut dyn $t<H>,
f: F
) -> R {
let lifetime_extended = unsafe {
$crate::transmute::<&mut dyn $t<H>, &mut (dyn $t<$concretetype> + 'static)>(protected)
};
$crate::using_once(&GLOBAL, lifetime_extended, f)
}
}
};
($name:ident : trait $t:ident <>) => { $crate::environmental! { $name : trait @$t [] } };
($name:ident : trait $t:ident < $($args:ty),* $(,)* >) => {
$crate::environmental! { $name : trait @$t [$($args,)*] }
};
($name:ident : trait $t:ident) => { $crate::environmental! { $name : trait @$t [] } };
}
#[cfg(test)]
mod tests {
#[allow(dead_code)]
mod trait_test {
trait Test {}
environmental!(item_positon_trait: trait Test);
}
#[allow(dead_code)]
mod type_test {
environmental!(item_position_type: u32);
}
#[test]
fn simple_works() {
environmental!(counter: u32);
fn stuff() { counter::with(|value| *value += 1); }
let mut local = 41u32;
counter::using(&mut local, stuff);
assert_eq!(local, 42);
stuff(); assert_eq!(local, 42);
}
#[test]
fn overwrite_with_lesser_lifetime() {
environmental!(items: Vec<u8>);
let mut local_items = vec![1, 2, 3];
items::using(&mut local_items, || {
let dies_at_end = vec![4, 5, 6];
items::with(|items| *items = dies_at_end);
});
assert_eq!(local_items, vec![4, 5, 6]);
}
#[test]
fn declare_with_trait_object() {
trait Foo {
fn get(&self) -> i32;
fn set(&mut self, x: i32);
}
impl Foo for i32 {
fn get(&self) -> i32 { *self }
fn set(&mut self, x: i32) { *self = x }
}
environmental!(foo: dyn Foo + 'static);
fn stuff() {
foo::with(|value| {
let new_val = value.get() + 1;
value.set(new_val);
});
}
let mut local = 41i32;
foo::using(&mut local, stuff);
assert_eq!(local, 42);
stuff(); assert_eq!(local, 42);
}
#[test]
fn unwind_recursive() {
use std::panic;
environmental!(items: Vec<u8>);
let panicked = panic::catch_unwind(|| {
let mut local_outer = vec![1, 2, 3];
items::using(&mut local_outer, || {
let mut local_inner = vec![4, 5, 6];
items::using(&mut local_inner, || {
panic!("are you unsafe?");
})
});
}).is_err();
assert!(panicked);
let mut was_cleared = true;
items::with(|_items| was_cleared = false);
assert!(was_cleared);
}
#[test]
fn use_non_static_trait() {
trait Sum { fn sum(&self) -> usize; }
impl Sum for &[usize] {
fn sum(&self) -> usize {
self.iter().fold(0, |a, c| a + c)
}
}
environmental!(sum: trait Sum);
let numbers = vec![1, 2, 3, 4, 5];
let mut numbers = &numbers[..];
let got_sum = sum::using(&mut numbers, || {
sum::with(|x| x.sum())
}).unwrap();
assert_eq!(got_sum, 15);
}
#[test]
fn stacking_globals() {
trait Sum { fn sum(&self) -> usize; }
impl Sum for &[usize] {
fn sum(&self) -> usize {
self.iter().fold(0, |a, c| a + c)
}
}
environmental!(sum: trait Sum);
let numbers = vec![1, 2, 3, 4, 5];
let mut numbers = &numbers[..];
let got_sum = sum::using(&mut numbers, || {
sum::with(|_| {
let numbers2 = vec![1, 2, 3, 4, 5, 6];
let mut numbers2 = &numbers2[..];
sum::using(&mut numbers2, || {
sum::with(|x| x.sum())
})
})
}).unwrap().unwrap();
assert_eq!(got_sum, 21);
assert!(sum::with(|_| ()).is_none());
}
#[test]
fn use_generic_trait() {
trait Plus { fn plus42() -> usize; }
struct ConcretePlus;
impl Plus for ConcretePlus {
fn plus42() -> usize { 42 }
}
trait Multiplier<T: Plus> { fn mul_and_add(&self) -> usize; }
impl<'a, P: Plus> Multiplier<P> for &'a [usize] {
fn mul_and_add(&self) -> usize {
self.iter().fold(1, |a, c| a * c) + P::plus42()
}
}
let numbers = vec![1, 2, 3];
let mut numbers = &numbers[..];
let out = foo::<ConcretePlus>::using(&mut numbers, || {
foo::<ConcretePlus>::with(|x| x.mul_and_add() )
}).unwrap();
assert_eq!(out, 6 + 42);
environmental!(foo<Plus>: trait Multiplier<ConcretePlus>);
}
#[test]
fn using_once_is_working() {
environmental!(value: u32);
let mut called_inner = false;
value::using_once(&mut 5, || {
value::using_once(&mut 10, || {
assert_eq!(5, value::with(|v| *v).unwrap());
value::using(&mut 20, || {
assert_eq!(20, value::with(|v| *v).unwrap());
value::using_once(&mut 30, || {
assert_eq!(20, value::with(|v| *v).unwrap());
called_inner = true;
})
})
})
});
assert!(called_inner);
}
}