use core::cell::RefCell;
use core::fmt;
use core::future::Future;
use core::marker::PhantomPinned;
use core::pin::Pin;
use core::task::{Context, Poll};
#[macro_export]
macro_rules! task_local {
() => {};
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty; $($rest:tt)*) => {
$crate::__task_local_key!($(#[$attr])* $vis $name, $t);
$crate::task_local!($($rest)*);
};
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty) => {
$crate::__task_local_key!($(#[$attr])* $vis $name, $t);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __task_local_key {
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty) => {
$(#[$attr])*
$vis static $name: $crate::task::LocalKey<$t> = {
::std::thread_local! {
static __KEY: ::core::cell::RefCell<::core::option::Option<$t>> =
const { ::core::cell::RefCell::new(::core::option::Option::None) };
}
$crate::task::LocalKey { inner: __KEY }
};
};
}
pub struct LocalKey<T: 'static> {
#[doc(hidden)]
pub inner: std::thread::LocalKey<RefCell<Option<T>>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AccessError {
_private: (),
}
impl fmt::Display for AccessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("task-local value not set")
}
}
impl std::error::Error for AccessError {}
impl<T: 'static> LocalKey<T> {
pub fn scope<F: Future>(&'static self, value: T, future: F) -> TaskLocalFuture<T, F> {
TaskLocalFuture {
key: self,
slot: Some(value),
future: Some(future),
_pinned: PhantomPinned,
}
}
pub fn sync_scope<F: FnOnce() -> R, R>(&'static self, value: T, f: F) -> R {
let mut slot = Some(value);
self.enter(&mut slot, f)
}
#[expect(
clippy::panic,
reason = "documented under `# Panics`; `try_with` answers the error instead"
)]
pub fn with<F: FnOnce(&T) -> R, R>(&'static self, f: F) -> R {
match self.try_with(f) {
Ok(r) => r,
Err(e) => panic!("{e}: no `scope` set this task-local key"),
}
}
pub fn try_with<F: FnOnce(&T) -> R, R>(&'static self, f: F) -> Result<R, AccessError> {
self.inner
.try_with(|cell| cell.borrow().as_ref().map(f))
.ok()
.flatten()
.ok_or(AccessError { _private: () })
}
pub fn get(&'static self) -> T
where
T: Clone,
{
self.with(T::clone)
}
#[expect(
clippy::panic,
reason = "a key used during this thread's teardown, where no value can be swapped in"
)]
fn enter<F: FnOnce() -> R, R>(&'static self, slot: &mut Option<T>, f: F) -> R {
struct Restore<'a, T: 'static> {
key: &'static LocalKey<T>,
slot: &'a mut Option<T>,
}
impl<T: 'static> Drop for Restore<'_, T> {
fn drop(&mut self) {
let _ = self.key.inner.try_with(|cell| {
core::mem::swap(&mut *cell.borrow_mut(), self.slot);
});
}
}
let swapped = self
.inner
.try_with(|cell| core::mem::swap(&mut *cell.borrow_mut(), slot));
if swapped.is_err() {
panic!("the task-local key is used while this thread's locals are torn down");
}
let _restore = Restore { key: self, slot };
f()
}
}
impl<T: 'static> fmt::Debug for LocalKey<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LocalKey").finish_non_exhaustive()
}
}
pub struct TaskLocalFuture<T: 'static, F> {
key: &'static LocalKey<T>,
slot: Option<T>,
future: Option<F>,
_pinned: PhantomPinned,
}
impl<T: 'static, F: Future> Future for TaskLocalFuture<T, F> {
type Output = F::Output;
#[expect(
clippy::panic,
reason = "polled after it completed, which the `Future` contract rules out"
)]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
let this = unsafe { self.get_unchecked_mut() };
let key = this.key;
let slot = &mut this.slot;
let future = &mut this.future;
key.enter(slot, || {
let Some(inner) = future.as_mut() else {
panic!("`TaskLocalFuture` polled after it completed");
};
let poll = unsafe { Pin::new_unchecked(inner) }.poll(cx);
if poll.is_ready() {
*future = None;
}
poll
})
}
}
impl<T: 'static, F> Drop for TaskLocalFuture<T, F> {
fn drop(&mut self) {
if self.future.is_some() {
let future = &mut self.future;
let key = self.key;
let entered = key.inner.try_with(|_| ()).is_ok();
if entered {
key.enter(&mut self.slot, || *future = None);
} else {
*future = None;
}
}
}
}
impl<T: 'static + fmt::Debug, F> fmt::Debug for TaskLocalFuture<T, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TaskLocalFuture")
.field("value", &self.slot)
.finish_non_exhaustive()
}
}