use std::{cell::RefCell, thread::LocalKey};
#[macro_export]
macro_rules! guarded_thread_local {
($(#[$attrs:meta])* $vis:vis static $name:ident: $ty:ty) => (
$(#[$attrs])*
$vis static $name: $crate::GuardedKey<$ty> = {
::std::thread_local!(static FOO: ::std::cell::RefCell<$crate::Inner<$ty>> = const {
::std::cell::RefCell::new($crate::Inner::new())
});
$crate::GuardedKey::new(&FOO)
};
)
}
pub struct GuardedKey<T: 'static> {
inner: &'static LocalKey<RefCell<Inner<T>>>,
}
impl<T: 'static> GuardedKey<T> {
#[doc(hidden)]
pub const fn new(inner: &'static LocalKey<RefCell<Inner<T>>>) -> Self {
Self { inner }
}
#[must_use]
pub fn set(&'static self, t: T) -> Guard<T> {
self.inner.with_borrow_mut(move |inner| {
inner.item.push(Some(t));
Guard {
inner: self.inner,
index: inner.item.len() - 1,
}
})
}
}
impl<T: Clone + 'static> GuardedKey<T> {
pub fn get(&'static self) -> T {
let Some(val) = self.inner.with_borrow(|inner| inner.item.last().cloned()) else {
panic!("cannot access a guarded thread local variable without calling `set` first")
};
val.expect("internal error: top of item list is none")
}
}
#[doc(hidden)]
pub struct Inner<T: 'static> {
item: Vec<Option<T>>,
}
impl<T: 'static> Inner<T> {
#[doc(hidden)]
pub const fn new() -> Self {
Self { item: Vec::new() }
}
}
pub struct Guard<T: 'static> {
inner: &'static LocalKey<RefCell<Inner<T>>>,
index: usize,
}
impl<T> Drop for Guard<T> {
fn drop(&mut self) {
self.inner.with_borrow_mut(|inner| {
*inner.item.get_mut(self.index).unwrap() = None;
while let Some(item) = inner.item.last() {
if item.is_none() {
let _ = inner.item.pop();
} else {
break;
}
}
});
}
}
#[cfg(test)]
mod tests {
#[test]
fn smoke() {
guarded_thread_local!(static FOO: u32);
let _foo_guard_1 = FOO.set(3);
assert_eq!(FOO.get(), 3);
assert_eq!(FOO.get(), 3);
let foo_guard_2 = FOO.set(123);
assert_eq!(FOO.get(), 123);
drop(foo_guard_2);
assert_eq!(FOO.get(), 3);
}
#[test]
#[should_panic(
expected = "cannot access a guarded thread local variable without calling `set` first"
)]
fn get_without_set() {
guarded_thread_local!(static FOO: u32);
let _ = FOO.get();
}
#[test]
fn out_of_order_guard_drop() {
guarded_thread_local!(static FOO: u32);
let guard_1 = FOO.set(1);
let guard_2 = FOO.set(2);
let guard_3 = FOO.set(3);
assert_eq!(FOO.get(), 3);
drop(guard_1);
assert_eq!(FOO.get(), 3);
drop(guard_3);
assert_eq!(FOO.get(), 2);
drop(guard_2);
}
#[test]
fn non_copy_type() {
guarded_thread_local!(static FOO: String);
let _guard_1 = FOO.set("x".into());
let guard_2 = FOO.set("y".into());
assert_eq!(FOO.get(), "y");
drop(guard_2);
assert_eq!(FOO.get(), "x");
}
#[test]
#[should_panic(expected = "already borrowed: BorrowMutError")]
fn clone_access_same_thread_local() {
guarded_thread_local!(static FOO: X);
struct X;
impl Clone for X {
fn clone(&self) -> Self {
let _ = FOO.set(X);
X
}
}
let _guard = FOO.set(X);
let _ = FOO.get();
}
}