Skip to main content

wide_log/
context.rs

1use std::cell::Cell;
2
3/// A thread-local cell storing a raw pointer to the current wide event.
4///
5/// This type provides safe methods for setting, getting, and saving/restoring
6/// the pointer, which is used by the generated guard to manage nested scopes.
7///
8/// # Safety Invariants
9///
10/// The raw pointer stored in this cell is sound because:
11///
12/// 1. **The guard owns the event.** The `WideEvent<K>` is a field of the guard,
13///    which is on the stack (or in the `scope()` future's state). The guard
14///    outlives any code that calls `current()` within the same scope.
15/// 2. **Set on creation, cleared on drop.** The guard's `new` sets the pointer;
16///    its `Drop` restores the previous pointer (or null) before the inner
17///    `ScopedGuard::Drop` runs.
18/// 3. **Nested scopes** save/restore the previous pointer via `replace` and the
19///    `prev_ptr` field on the guard.
20/// 4. **No cross-thread access.** `thread_local!` means each thread has its own
21///    cell. For async, `tokio::task_local!` moves with the task across threads.
22///
23/// The `&'static` lifetime returned by accessors is a "valid for the duration
24/// of the guard" lifetime — the standard pattern used by `thread_local`
25/// accessors (e.g., `tracing`'s `Span::current()`).
26pub struct ContextCell<T> {
27    ptr: Cell<*mut T>,
28}
29
30impl<T> ContextCell<T> {
31    /// Create a new cell initialized to a null pointer.
32    #[inline]
33    pub const fn new() -> Self {
34        Self {
35            ptr: Cell::new(std::ptr::null_mut()),
36        }
37    }
38
39    /// Returns the stored raw pointer (null if unset).
40    #[inline]
41    pub fn get_ptr(&self) -> *mut T {
42        self.ptr.get()
43    }
44
45    /// Sets the stored pointer. Returns the previous pointer.
46    #[inline]
47    pub fn replace(&self, ptr: *mut T) -> *mut T {
48        let prev = self.ptr.get();
49        self.ptr.set(ptr);
50        prev
51    }
52
53    /// Restores a previously saved pointer (typically from `replace` or `clear`).
54    #[inline]
55    pub fn restore(&self, ptr: *mut T) {
56        self.ptr.set(ptr);
57    }
58}
59
60#[cfg(test)]
61impl<T> ContextCell<T> {
62    /// Returns the stored pointer, or `None` if null.
63    #[inline]
64    pub(crate) fn get(&self) -> Option<*mut T> {
65        let ptr = self.ptr.get();
66        if ptr.is_null() { None } else { Some(ptr) }
67    }
68
69    /// Clears the stored pointer to null. Returns the previous pointer.
70    #[inline]
71    pub(crate) fn clear(&self) -> *mut T {
72        self.replace(std::ptr::null_mut())
73    }
74
75    /// Returns a `&'static mut T` reference to the event if the pointer is non-null.
76    ///
77    /// # Safety
78    ///
79    /// The caller must ensure the pointer is valid and that the referenced `T`
80    /// will not be accessed mutably from another reference for the duration of
81    /// the returned borrow. In practice, this is guaranteed by the guard's
82    /// lifetime — the guard outlives all `current()` calls within its scope.
83    #[inline]
84    pub(crate) unsafe fn deref_mut(&self) -> Option<&'static mut T> {
85        let ptr = self.ptr.get();
86        if ptr.is_null() {
87            None
88        } else {
89            Some(unsafe { &mut *ptr })
90        }
91    }
92}
93
94impl<T> Default for ContextCell<T> {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100// SAFETY: The raw pointer inside `ContextCell` is only accessed via the
101// thread-local or task-local storage. For thread-local access, each thread
102// has its own cell, so there is no cross-thread sharing. For task-local
103// access, the task moves as a unit across threads — the pointer is only
104// dereferenced on the thread currently executing the task. The pointer is
105// never accessed concurrently from multiple threads.
106unsafe impl<T> Send for ContextCell<T> {}
107unsafe impl<T> Sync for ContextCell<T> {}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn new_is_null() {
115        let cell = ContextCell::<u32>::new();
116        assert!(cell.get().is_none());
117    }
118
119    #[test]
120    fn replace_returns_previous() {
121        let cell = ContextCell::<u32>::new();
122        let mut a: u32 = 1;
123        let mut b: u32 = 2;
124        let ptr_a = &mut a as *mut u32;
125        let ptr_b = &mut b as *mut u32;
126
127        let prev = cell.replace(ptr_a);
128        assert!(prev.is_null());
129        assert_eq!(cell.get(), Some(ptr_a));
130
131        let prev = cell.replace(ptr_b);
132        assert_eq!(prev, ptr_a);
133        assert_eq!(cell.get(), Some(ptr_b));
134    }
135
136    #[test]
137    fn clear_returns_previous() {
138        let cell = ContextCell::<u32>::new();
139        let mut a: u32 = 42;
140        let ptr_a = &mut a as *mut u32;
141
142        cell.replace(ptr_a);
143        let prev = cell.clear();
144        assert_eq!(prev, ptr_a);
145        assert!(cell.get().is_none());
146    }
147
148    #[test]
149    fn restore_sets_pointer() {
150        let cell = ContextCell::<u32>::new();
151        let mut a: u32 = 10;
152        let ptr_a = &mut a as *mut u32;
153
154        cell.restore(ptr_a);
155        assert_eq!(cell.get(), Some(ptr_a));
156    }
157
158    #[test]
159    fn deref_mut_returns_reference() {
160        let cell = ContextCell::<u32>::new();
161        let mut a: u32 = 99;
162        let ptr_a = &mut a as *mut u32;
163        cell.replace(ptr_a);
164
165        let r = unsafe { cell.deref_mut() }.unwrap();
166        assert_eq!(*r, 99);
167        *r = 100;
168        assert_eq!(a, 100);
169    }
170
171    #[test]
172    fn deref_mut_none_when_null() {
173        let cell = ContextCell::<u32>::new();
174        assert!(unsafe { cell.deref_mut() }.is_none());
175    }
176
177    #[test]
178    fn default_is_null() {
179        let cell = ContextCell::<u32>::default();
180        assert!(cell.get().is_none());
181    }
182}