future_local_storage/lib.rs
1//! # Overview
2#![doc = include_utils::include_md!("README.md:description")]
3//!
4//! ## Examples
5//!
6//! ### Tracing spans
7//!
8//! ```rust
9#![doc = include_str!("../examples/context/mod.rs")]
10//!
11//! // Usage example
12//!
13//! async fn some_method(mut a: u64) -> u64 {
14//! TracerContext::on_enter(format!("`some_method` with params: a={a}"));
15//!
16//! // Some async computation
17//!
18//! TracerContext::on_exit("`some_method`");
19//! a * 32
20//! }
21//!
22//! #[tokio::main]
23//! async fn main() {
24//! let (trace, result) = TracerContext::in_scope(some_method(45)).await;
25//!
26//! println!("answer: {result}");
27//! println!("trace: {trace:#?}");
28//! }
29//! ```
30
31use std::{fmt::Debug, future::Future};
32
33use future::ScopedFutureWithValue;
34use imp::FutureLocalKey;
35
36pub mod future;
37mod imp;
38
39/// An init-once-per-future cell for thread-local values.
40///
41/// It uses thread local storage to ensure that the each polled future has its own local storage key.
42/// Unlike the [`std::thread::LocalKey`] this cell will *not* lazily initialize the value on first access.
43/// Instead, the value is first initialized when the future containing the future-local is first polled
44/// by an executor.
45///
46/// After the execution finished the value moves from the future local cell to the future output.
47pub struct FutureOnceCell<T>(imp::FutureLocalKey<T>);
48
49impl<T> FutureOnceCell<T> {
50 /// Creates an empty future once cell.
51 #[must_use]
52 pub const fn new() -> Self {
53 Self(imp::FutureLocalKey::new())
54 }
55}
56
57impl<T> Default for FutureOnceCell<T> {
58 #[must_use]
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl<T: Send + 'static> FutureOnceCell<T> {
65 /// Acquires a reference to the value in this future local storage.
66 ///
67 /// Unlike the [`std::thread::LocalKey::with`] this method does not initialize the value
68 /// when called.
69 ///
70 /// # Panics
71 ///
72 /// - This method will panic if the future local doesn't have a value set.
73 ///
74 /// - If you the returned future inside the a call to [`Self::with`] on the same cell, then the
75 /// call to `poll` will panic.
76 #[inline]
77 pub fn with<F, R>(&'static self, f: F) -> R
78 where
79 F: FnOnce(&T) -> R,
80 {
81 let value = self.0.local_key().borrow();
82 f(value
83 .as_ref()
84 .expect("cannot access a future local value without setting it first"))
85 }
86
87 /// Returns a copy of the contained value.
88 ///
89 /// # Panics
90 ///
91 /// This method will panic if the future local doesn't have a value set.
92 #[inline]
93 pub fn get(&'static self) -> T
94 where
95 T: Copy,
96 {
97 self.0.local_key().borrow().unwrap()
98 }
99
100 /// Sets a value `T` as the future-local value for the future `F`.
101 ///
102 /// On completion of `scope`, the future-local value will be returned by the scoped future.
103 ///
104 /// ```rust
105 /// use std::cell::Cell;
106 ///
107 /// use future_local_storage::FutureOnceCell;
108 ///
109 /// static VALUE: FutureOnceCell<Cell<u64>> = FutureOnceCell::new();
110 ///
111 /// #[tokio::main]
112 /// async fn main() {
113 /// let (output, answer) = VALUE.scope(Cell::from(0), async {
114 /// VALUE.with(|x| {
115 /// let value = x.get();
116 /// x.set(value + 1);
117 /// });
118 ///
119 /// 42
120 /// }).await;
121 /// }
122 /// ```
123 #[inline]
124 pub fn scope<F>(&'static self, value: T, future: F) -> ScopedFutureWithValue<T, F>
125 where
126 F: Future,
127 {
128 future.with_scope(self, value)
129 }
130}
131
132impl<T: Debug + Send + 'static> Debug for FutureOnceCell<T> {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 f.debug_tuple("FutureOnceCell").field(&self.0).finish()
135 }
136}
137
138impl<T> AsRef<FutureLocalKey<T>> for FutureOnceCell<T> {
139 fn as_ref(&self) -> &FutureLocalKey<T> {
140 &self.0
141 }
142}
143
144/// Attaches future local storage values to a [`Future`].
145///
146/// Extension trait allowing futures to have their own static variables.
147pub trait FutureLocalStorage: Future + Sized + private::Sealed {
148 /// Sets a given value as the future local value of this future.
149 ///
150 /// Each future instance will have its own state of the attached value.
151 ///
152 /// ```rust
153 /// use std::cell::Cell;
154 ///
155 /// use future_local_storage::{FutureOnceCell, FutureLocalStorage};
156 ///
157 /// static VALUE: FutureOnceCell<Cell<u64>> = FutureOnceCell::new();
158 ///
159 /// #[tokio::main]
160 /// async fn main() {
161 /// let (output, answer) = async {
162 /// VALUE.with(|x| {
163 /// let value = x.get();
164 /// x.set(value + 1);
165 /// });
166 ///
167 /// 42
168 /// }.with_scope(&VALUE, Cell::from(0)).await;
169 /// }
170 /// ```
171 fn with_scope<T, S>(self, scope: &'static S, value: T) -> ScopedFutureWithValue<T, Self>
172 where
173 T: Send,
174 S: AsRef<FutureLocalKey<T>>;
175}
176
177mod private {
178 use std::future::Future;
179
180 pub trait Sealed {}
181
182 impl<F: Future> Sealed for F {}
183}
184
185#[cfg(test)]
186mod tests {
187 use std::cell::{Cell, RefCell};
188
189 use pretty_assertions::assert_eq;
190
191 use super::*;
192 use crate::FutureLocalStorage;
193
194 #[test]
195 fn test_once_cell_without_future() {
196 static LOCK: FutureOnceCell<RefCell<String>> = FutureOnceCell::new();
197 LOCK.0
198 .local_key()
199 .borrow_mut()
200 .replace(RefCell::new("0".to_owned()));
201
202 assert_eq!(LOCK.with(|x| x.borrow().clone()), "0".to_owned());
203 LOCK.with(|x| x.replace("42".to_owned()));
204 assert_eq!(LOCK.with(|x| x.borrow().clone()), "42".to_owned());
205 }
206
207 #[tokio::test]
208 async fn test_future_once_cell_output() {
209 static VALUE: FutureOnceCell<Cell<u64>> = FutureOnceCell::new();
210
211 let (output, ()) = VALUE
212 .scope(Cell::from(0), async {
213 VALUE.with(|x| {
214 let value = x.get();
215 x.set(value + 1);
216 });
217 })
218 .await;
219
220 assert_eq!(output.into_inner(), 1);
221 }
222
223 #[tokio::test]
224 async fn test_future_once_cell_discard_value() {
225 static VALUE: FutureOnceCell<Cell<u64>> = FutureOnceCell::new();
226
227 let fut_1 = async {
228 for _ in 0..42 {
229 VALUE.with(|x| {
230 let value = x.get();
231 x.set(value + 1);
232 });
233 tokio::task::yield_now().await;
234 }
235
236 VALUE.with(Cell::get)
237 }
238 .with_scope(&VALUE, Cell::new(0))
239 .discard_value();
240
241 let fut_2 = async { VALUE.with(Cell::get) }
242 .with_scope(&VALUE, Cell::new(15))
243 .discard_value();
244
245 assert_eq!(fut_1.await, 42);
246 assert_eq!(fut_2.await, 15);
247 assert_eq!(
248 tokio::spawn(
249 async { VALUE.with(Cell::get) }
250 .with_scope(&VALUE, Cell::new(115))
251 .discard_value()
252 )
253 .await
254 .unwrap(),
255 115
256 );
257 }
258}