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}