1use core::cell::RefCell;
9use core::fmt;
10use core::future::Future;
11use core::marker::PhantomPinned;
12use core::pin::Pin;
13use core::task::{Context, Poll};
14
15#[macro_export]
31macro_rules! task_local {
32 () => {};
33 ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty; $($rest:tt)*) => {
34 $crate::__task_local_key!($(#[$attr])* $vis $name, $t);
35 $crate::task_local!($($rest)*);
36 };
37 ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty) => {
38 $crate::__task_local_key!($(#[$attr])* $vis $name, $t);
39 };
40}
41
42#[doc(hidden)]
43#[macro_export]
44macro_rules! __task_local_key {
45 ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty) => {
46 $(#[$attr])*
47 $vis static $name: $crate::task::LocalKey<$t> = {
48 ::std::thread_local! {
49 static __KEY: ::core::cell::RefCell<::core::option::Option<$t>> =
50 const { ::core::cell::RefCell::new(::core::option::Option::None) };
51 }
52 $crate::task::LocalKey { inner: __KEY }
53 };
54 };
55}
56
57pub struct LocalKey<T: 'static> {
59 #[doc(hidden)]
60 pub inner: std::thread::LocalKey<RefCell<Option<T>>>,
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub struct AccessError {
66 _private: (),
67}
68
69impl fmt::Display for AccessError {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.write_str("task-local value not set")
72 }
73}
74
75impl std::error::Error for AccessError {}
76
77impl<T: 'static> LocalKey<T> {
78 pub fn scope<F: Future>(&'static self, value: T, future: F) -> TaskLocalFuture<T, F> {
82 TaskLocalFuture {
83 key: self,
84 slot: Some(value),
85 future: Some(future),
86 _pinned: PhantomPinned,
87 }
88 }
89
90 pub fn sync_scope<F: FnOnce() -> R, R>(&'static self, value: T, f: F) -> R {
92 let mut slot = Some(value);
93 self.enter(&mut slot, f)
94 }
95
96 #[expect(
103 clippy::panic,
104 reason = "documented under `# Panics`; `try_with` answers the error instead"
105 )]
106 pub fn with<F: FnOnce(&T) -> R, R>(&'static self, f: F) -> R {
107 match self.try_with(f) {
108 Ok(r) => r,
109 Err(e) => panic!("{e}: no `scope` set this task-local key"),
110 }
111 }
112
113 pub fn try_with<F: FnOnce(&T) -> R, R>(&'static self, f: F) -> Result<R, AccessError> {
116 self.inner
117 .try_with(|cell| cell.borrow().as_ref().map(f))
118 .ok()
119 .flatten()
120 .ok_or(AccessError { _private: () })
121 }
122
123 pub fn get(&'static self) -> T
129 where
130 T: Clone,
131 {
132 self.with(T::clone)
133 }
134
135 #[expect(
138 clippy::panic,
139 reason = "a key used during this thread's teardown, where no value can be swapped in"
140 )]
141 fn enter<F: FnOnce() -> R, R>(&'static self, slot: &mut Option<T>, f: F) -> R {
142 struct Restore<'a, T: 'static> {
143 key: &'static LocalKey<T>,
144 slot: &'a mut Option<T>,
145 }
146 impl<T: 'static> Drop for Restore<'_, T> {
147 fn drop(&mut self) {
148 let _ = self.key.inner.try_with(|cell| {
149 core::mem::swap(&mut *cell.borrow_mut(), self.slot);
150 });
151 }
152 }
153 let swapped = self
154 .inner
155 .try_with(|cell| core::mem::swap(&mut *cell.borrow_mut(), slot));
156 if swapped.is_err() {
157 panic!("the task-local key is used while this thread's locals are torn down");
158 }
159 let _restore = Restore { key: self, slot };
160 f()
161 }
162}
163
164impl<T: 'static> fmt::Debug for LocalKey<T> {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 f.debug_struct("LocalKey").finish_non_exhaustive()
167 }
168}
169
170pub struct TaskLocalFuture<T: 'static, F> {
172 key: &'static LocalKey<T>,
173 slot: Option<T>,
175 future: Option<F>,
177 _pinned: PhantomPinned,
178}
179
180impl<T: 'static, F: Future> Future for TaskLocalFuture<T, F> {
181 type Output = F::Output;
182
183 #[expect(
184 clippy::panic,
185 reason = "polled after it completed, which the `Future` contract rules out"
186 )]
187 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
188 let this = unsafe { self.get_unchecked_mut() };
192 let key = this.key;
193 let slot = &mut this.slot;
194 let future = &mut this.future;
195 key.enter(slot, || {
196 let Some(inner) = future.as_mut() else {
197 panic!("`TaskLocalFuture` polled after it completed");
198 };
199 let poll = unsafe { Pin::new_unchecked(inner) }.poll(cx);
201 if poll.is_ready() {
202 *future = None;
203 }
204 poll
205 })
206 }
207}
208
209impl<T: 'static, F> Drop for TaskLocalFuture<T, F> {
210 fn drop(&mut self) {
211 if self.future.is_some() {
212 let future = &mut self.future;
213 let key = self.key;
217 let entered = key.inner.try_with(|_| ()).is_ok();
218 if entered {
219 key.enter(&mut self.slot, || *future = None);
220 } else {
221 *future = None;
222 }
223 }
224 }
225}
226
227impl<T: 'static + fmt::Debug, F> fmt::Debug for TaskLocalFuture<T, F> {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 f.debug_struct("TaskLocalFuture")
230 .field("value", &self.slot)
231 .finish_non_exhaustive()
232 }
233}