use std::borrow::Borrow;
use std::cell::UnsafeCell;
use std::mem;
use std::thread::LocalKey;
#[cfg(feature = "static-init")]
#[macro_export]
macro_rules! fluid_let {
{
$(#[$attr:meta])*
$pub:vis static $name:ident: $type:ty
} => {
$(#[$attr])*
$pub static $name: $crate::DynamicVariable<$type> = {
thread_local! {
static VARIABLE: $crate::DynamicCell<$type> = $crate::DynamicCell::empty();
}
$crate::DynamicVariable::new(&VARIABLE)
};
};
{
$(#[$attr:meta])*
$pub:vis static $name:ident: $type:ty = $value:expr
} => {
$(#[$attr])*
$pub static $name: $crate::DynamicVariable<$type> = {
static DEFAULT: $type = $value;
thread_local! {
static VARIABLE: $crate::DynamicCell<$type> = $crate::DynamicCell::with_static(&DEFAULT);
}
$crate::DynamicVariable::new(&VARIABLE)
};
};
{
$(#[$attr:meta])*
$pub:vis static $name:ident: $type:ty;
$($rest:tt)*
} => {
$crate::fluid_let!($(#[$attr])* $pub static $name: $type);
$crate::fluid_let!($($rest)*);
};
{
$(#[$attr:meta])*
$pub:vis static $name:ident: $type:ty = $value:expr;
$($rest:tt)*
} => {
$crate::fluid_let!($(#[$attr])* $pub static $name: $type = $value);
$crate::fluid_let!($($rest)*);
};
{} => {};
}
#[cfg(not(feature = "static-init"))]
#[macro_export]
macro_rules! fluid_let {
{
$(#[$attr:meta])*
$pub:vis static $name:ident: $type:ty
} => {
$(#[$attr])*
$pub static $name: $crate::DynamicVariable<$type> = {
thread_local! {
static VARIABLE: $crate::DynamicCell<$type> = $crate::DynamicCell::empty();
}
$crate::DynamicVariable::new(&VARIABLE)
};
};
{
$(#[$attr:meta])*
$pub:vis static $name:ident: $type:ty = $value:expr
} => {
compile_error!("Static initialization is unstable, use \"static-init\" feature to opt-in");
};
{
$(#[$attr:meta])*
$pub:vis static $name:ident: $type:ty;
$($rest:tt)*
} => {
$crate::fluid_let!($(#[$attr])* $pub static $name: $type);
$crate::fluid_let!($($rest)*);
};
{
$(#[$attr:meta])*
$pub:vis static $name:ident: $type:ty = $value:expr;
$($rest:tt)*
} => {
$crate::fluid_let!($(#[$attr])* $pub static $name: $type = $value);
$crate::fluid_let!($($rest)*);
};
{} => {};
}
#[macro_export]
macro_rules! fluid_set {
($variable:expr, $value:expr) => {
let _value_ = $value;
let _guard_ = unsafe { $variable.set_guard(&_value_) };
};
}
pub struct DynamicVariable<T: 'static> {
cell: &'static LocalKey<DynamicCell<T>>,
}
#[doc(hidden)]
pub struct DynamicCell<T> {
cell: UnsafeCell<Option<*const T>>,
}
#[doc(hidden)]
pub struct DynamicCellGuard<'a, T> {
old_value: Option<*const T>,
cell: &'a DynamicCell<T>,
}
impl<T> DynamicVariable<T> {
#[doc(hidden)]
pub const fn new(cell: &'static LocalKey<DynamicCell<T>>) -> Self {
Self { cell }
}
pub fn get<R>(&self, f: impl FnOnce(Option<&T>) -> R) -> R {
self.cell.with(|current| {
f(unsafe { current.get() })
})
}
pub fn set<R>(&self, value: impl Borrow<T>, f: impl FnOnce() -> R) -> R {
self.cell.with(|current| {
let _guard_ = unsafe { current.set(value.borrow()) };
f()
})
}
#[doc(hidden)]
pub unsafe fn set_guard(&self, value: &T) -> DynamicCellGuard<T> {
unsafe fn extend_lifetime<'a, 'b, T>(r: &'a T) -> &'b T {
mem::transmute(r)
}
self.cell
.with(|current| extend_lifetime(current).set(value))
}
}
impl<T: Clone> DynamicVariable<T> {
pub fn cloned(&self) -> Option<T> {
self.get(|value| value.cloned())
}
}
impl<T: Copy> DynamicVariable<T> {
pub fn copied(&self) -> Option<T> {
self.get(|value| value.copied())
}
}
impl<T> DynamicCell<T> {
pub fn empty() -> Self {
DynamicCell {
cell: UnsafeCell::new(None),
}
}
#[cfg(feature = "static-init")]
pub fn with_static(value: &'static T) -> Self {
DynamicCell {
cell: UnsafeCell::new(Some(value)),
}
}
unsafe fn get(&self) -> Option<&T> {
(&*self.cell.get()).map(|p| &*p)
}
unsafe fn set(&self, value: &T) -> DynamicCellGuard<T> {
DynamicCellGuard {
old_value: mem::replace(&mut *self.cell.get(), Some(value)),
cell: self,
}
}
}
impl<'a, T> Drop for DynamicCellGuard<'a, T> {
fn drop(&mut self) {
unsafe {
*self.cell.cell.get() = self.old_value.take();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt;
use std::thread;
#[test]
fn cell_set_get_guards() {
unsafe {
let v = DynamicCell::empty();
assert_eq!(v.get(), None);
{
let _g = v.set(&5);
assert_eq!(v.get(), Some(&5));
{
let _g = v.set(&10);
assert_eq!(v.get(), Some(&10));
}
assert_eq!(v.get(), Some(&5));
}
}
}
#[test]
fn cell_unsafe_set_get_usage() {
unsafe {
let v = DynamicCell::empty();
let g1 = v.set(&5);
let g2 = v.set(&10);
assert_eq!(v.get(), Some(&10));
drop(g1);
assert_eq!(v.get(), None);
drop(g2);
assert_eq!(v.get(), Some(&5));
}
}
#[test]
#[cfg(feature = "static-init")]
fn static_initializer() {
fluid_let!(static NUMBER: i32 = 42);
assert_eq!(NUMBER.copied(), Some(42));
fluid_let! {
static NUMBER_1: i32 = 100;
static NUMBER_2: i32;
static NUMBER_3: i32 = 200;
}
assert_eq!(NUMBER_1.copied(), Some(100));
assert_eq!(NUMBER_2.copied(), None);
assert_eq!(NUMBER_3.copied(), Some(200));
}
#[test]
fn dynamic_scoping() {
fluid_let!(static YEAR: i32);
YEAR.get(|current| assert_eq!(current, None));
fluid_set!(YEAR, 2019);
YEAR.get(|current| assert_eq!(current, Some(&2019)));
{
fluid_set!(YEAR, 2525);
YEAR.get(|current| assert_eq!(current, Some(&2525)));
}
YEAR.get(|current| assert_eq!(current, Some(&2019)));
}
#[test]
fn references() {
fluid_let!(static YEAR: i32);
fluid_set!(YEAR, 10);
assert_eq!(YEAR.copied(), Some(10));
let current_year = 20;
fluid_set!(YEAR, ¤t_year);
assert_eq!(YEAR.copied(), Some(20));
let current_year = Box::new(30);
fluid_set!(YEAR, current_year);
assert_eq!(YEAR.copied(), Some(30));
}
#[test]
fn thread_locality() {
fluid_let!(static THREAD_ID: i8);
THREAD_ID.set(0, || {
THREAD_ID.get(|current| assert_eq!(current, Some(&0)));
let t = thread::spawn(move || {
THREAD_ID.get(|current| assert_eq!(current, None));
THREAD_ID.set(1, || {
THREAD_ID.get(|current| assert_eq!(current, Some(&1)));
});
});
drop(t.join());
})
}
#[test]
fn convenience_accessors() {
fluid_let!(static ENABLED: bool);
assert_eq!(ENABLED.cloned(), None);
assert_eq!(ENABLED.copied(), None);
ENABLED.set(true, || assert_eq!(ENABLED.cloned(), Some(true)));
ENABLED.set(true, || assert_eq!(ENABLED.copied(), Some(true)));
}
struct Hash {
value: [u8; 16],
}
fluid_let!(pub static DEBUG_FULL_HASH: bool);
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let full = DEBUG_FULL_HASH.copied().unwrap_or(false);
write!(f, "Hash(")?;
if full {
for byte in &self.value {
write!(f, "{:02X}", byte)?;
}
} else {
for byte in &self.value[..4] {
write!(f, "{:02X}", byte)?;
}
write!(f, "...")?;
}
write!(f, ")")
}
}
#[test]
fn readme_example_code() {
let hash = Hash { value: [0; 16] };
assert_eq!(format!("{:?}", hash), "Hash(00000000...)");
fluid_set!(DEBUG_FULL_HASH, true);
assert_eq!(
format!("{:?}", hash),
"Hash(00000000000000000000000000000000)"
);
}
}