stack_tokens/
lib.rs

1//! This library implements stack tokens which can be used to safely borrow
2//! values with stack-local lifetimes.
3//!
4//! [`StackToken`]s are zero sized objects that can be placed on the call
5//! stack with the [`stack_token!`] macro which then can be used to safely
6//! borrow from places such as thread local storage with reduced lifetimes.
7//!
8//! Without stack tokens the only safe API for such constructs are callback
9//! based such as the [`LocalKey::with`](std::thread::LocalKey::with) API.
10//! This problem however is not always restricted to thread local storage
11//! directly as some APIs are internally constrained by similar challenges.
12//!
13//! The problem usually appears when a proxy object wants to lend out some
14//! memory but it does not have a better lifetime than itself to constrain
15//! the value, but it does not directly own the value it's trying to lend
16//! out.  As a Rust programmer one is enticed to try to constrain it by
17//! the lifetime of `&self` but thanks to [`Box::leak`] that lifetime can
18//! become `&'static`.
19//!
20//! For more information see the [the blog post describing the concept](https://lucumr.pocoo.org/2022/11/23/stack-tokens/).
21//!
22//! # Ref Cells
23//!
24//! This example shows how stack tokens can be used with the
25//! [`RefCellLocalKeyExt`] extension trait to directly borrow into [`RefCell`]s
26//! in a thread local.
27//!
28//! ```
29//! use stack_tokens::{stack_token, RefCellLocalKeyExt};
30//! use std::cell::RefCell;
31//!
32//! thread_local! {
33//!     static VEC: RefCell<Vec<i32>> = RefCell::default();
34//! }
35//!
36//! // places a token on the stack.
37//! stack_token!(scope);
38//!
39//! // you can now directly deref the thread local without closures
40//! VEC.as_mut(scope).push(42);
41//! assert_eq!(VEC.as_ref(scope).len(), 1);
42//! ```
43//!
44//! # Basic Use
45//!
46//! This example shows how stack tokens can be used with the [`LocalKeyExt`]
47//! extension trait to get stack local borrows from a standard library thread
48//! local.
49//!
50//! ```
51//! use stack_tokens::{stack_token, LocalKeyExt};
52//! use std::sync::atomic::{AtomicUsize, Ordering};
53//!
54//! thread_local! {
55//!     static COUNTER: AtomicUsize = AtomicUsize::new(0);
56//! }
57//!
58//! // places a token on the stack
59//! stack_token!(scope);
60//!
61//! // borrow can be used to get a stack local reference
62//! COUNTER.borrow(scope).fetch_add(1, Ordering::Acquire);
63//! assert_eq!(COUNTER.borrow(scope).load(Ordering::Acquire), 1);
64//! ```
65//!
66//! # Implementing Stack Local APIs
67//!
68//! To implement your own methods that use stack tokens introduce a new lifetime
69//! (eg: `'stack`) and constrain both `&self` and the passed token with it:
70//!
71//! ```
72//! use stack_tokens::StackToken;
73//! use std::marker::PhantomData;
74//!
75//! struct MyTls<T>(PhantomData::<T>);
76//!
77//! impl<T> MyTls<T> {
78//!     pub fn get<'stack>(&'stack self, token: &'stack StackToken) -> &'stack T {
79//!         let _ = token;
80//!         todo!()
81//!     }
82//! }
83//! ```
84use std::cell::{Ref, RefCell, RefMut};
85use std::marker::PhantomData;
86use std::mem::transmute;
87use std::thread::LocalKey;
88
89/// A token to bind lifetimes to a specific stack.
90///
91/// For more information see [`stack_token`].
92pub struct StackToken {
93    _marker: PhantomData<*const ()>,
94}
95
96impl StackToken {
97    #[doc(hidden)]
98    pub unsafe fn __private_new() -> StackToken {
99        StackToken {
100            _marker: PhantomData,
101        }
102    }
103}
104
105/// Creates a new [`StackToken`] with a given name on the stack.
106#[macro_export]
107macro_rules! stack_token {
108    ($name:ident) => {
109        #[allow(unsafe_code)]
110        let $name = &unsafe { $crate::StackToken::__private_new() };
111    };
112}
113
114/// Adds [`StackToken`] support to the standard library's [`LocalKey`].
115pub trait LocalKeyExt<T> {
116    /// Borrows the value from the TLS with a [`StackToken`].
117    fn borrow<'stack>(&'static self, token: &'stack StackToken) -> &'stack T;
118}
119
120impl<T: 'static> LocalKeyExt<T> for LocalKey<T> {
121    fn borrow<'stack>(&'static self, token: &'stack StackToken) -> &'stack T {
122        let _ = token;
123        self.with(|value| unsafe { transmute::<&T, &'stack T>(value) })
124    }
125}
126
127/// Additional utility methods to [`LocalKey`]s holding [`RefCell`] values.
128///
129/// This extension traits provides the two methods [`as_ref`](Self::as_ref)
130/// and [`as_mut`](Self::as_mut) that let you directly borrow into the
131/// contained [`RefCell`] with a [`StackToken`].
132pub trait RefCellLocalKeyExt<T> {
133    /// Acquires a reference to the contained value.
134    fn as_ref<'stack>(&'static self, token: &'stack StackToken) -> Ref<'stack, T>;
135
136    /// Acquires a mutable reference to the contained value.
137    fn as_mut<'stack>(&'static self, token: &'stack StackToken) -> RefMut<'stack, T>;
138}
139
140impl<T: 'static> RefCellLocalKeyExt<T> for LocalKey<RefCell<T>> {
141    fn as_ref<'stack>(&'static self, token: &'stack StackToken) -> Ref<'stack, T> {
142        self.borrow(token).borrow()
143    }
144
145    fn as_mut<'stack>(&'static self, token: &'stack StackToken) -> RefMut<'stack, T> {
146        self.borrow(token).borrow_mut()
147    }
148}
149
150#[test]
151fn test_tls_basic() {
152    use crate::stack_token;
153    use std::cell::RefCell;
154
155    thread_local! { static FOO: RefCell<u32> = RefCell::default(); }
156
157    stack_token!(scope);
158    *FOO.borrow(scope).borrow_mut() += 1;
159    assert_eq!(*FOO.borrow(scope).borrow(), 1);
160}
161
162#[test]
163fn test_tls_ref_cell() {
164    use crate::stack_token;
165    use std::cell::RefCell;
166
167    thread_local! { static FOO: RefCell<u32> = RefCell::default(); }
168
169    stack_token!(scope);
170    *FOO.as_mut(scope) += 1;
171    assert_eq!(*FOO.as_ref(scope), 1);
172}