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 pointer, or `None` if null.
40    #[inline]
41    pub fn get(&self) -> Option<*mut T> {
42        let ptr = self.ptr.get();
43        if ptr.is_null() { None } else { Some(ptr) }
44    }
45
46    /// Sets the stored pointer. Returns the previous pointer.
47    #[inline]
48    pub fn replace(&self, ptr: *mut T) -> *mut T {
49        let prev = self.ptr.get();
50        self.ptr.set(ptr);
51        prev
52    }
53
54    /// Clears the stored pointer to null. Returns the previous pointer.
55    #[inline]
56    pub fn clear(&self) -> *mut T {
57        self.replace(std::ptr::null_mut())
58    }
59
60    /// Restores a previously saved pointer (typically from `replace` or `clear`).
61    #[inline]
62    pub fn restore(&self, ptr: *mut T) {
63        self.ptr.set(ptr);
64    }
65
66    /// Returns a `&'static mut T` reference to the event if the pointer is non-null.
67    ///
68    /// # Safety
69    ///
70    /// The caller must ensure the pointer is valid and that the referenced `T`
71    /// will not be accessed mutably from another reference for the duration of
72    /// the returned borrow. In practice, this is guaranteed by the guard's
73    /// lifetime — the guard outlives all `current()` calls within its scope.
74    #[inline]
75    pub unsafe fn deref_mut(&self) -> Option<&'static mut T> {
76        let ptr = self.ptr.get();
77        if ptr.is_null() {
78            None
79        } else {
80            Some(unsafe { &mut *ptr })
81        }
82    }
83}
84
85impl<T> Default for ContextCell<T> {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91// SAFETY: The raw pointer inside `ContextCell` is only accessed via the
92// thread-local or task-local storage. For thread-local access, each thread
93// has its own cell, so there is no cross-thread sharing. For task-local
94// access, the task moves as a unit across threads — the pointer is only
95// dereferenced on the thread currently executing the task. The pointer is
96// never accessed concurrently from multiple threads.
97unsafe impl<T> Send for ContextCell<T> {}
98unsafe impl<T> Sync for ContextCell<T> {}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn new_is_null() {
106        let cell = ContextCell::<u32>::new();
107        assert!(cell.get().is_none());
108    }
109
110    #[test]
111    fn replace_returns_previous() {
112        let cell = ContextCell::<u32>::new();
113        let mut a: u32 = 1;
114        let mut b: u32 = 2;
115        let ptr_a = &mut a as *mut u32;
116        let ptr_b = &mut b as *mut u32;
117
118        let prev = cell.replace(ptr_a);
119        assert!(prev.is_null());
120        assert_eq!(cell.get(), Some(ptr_a));
121
122        let prev = cell.replace(ptr_b);
123        assert_eq!(prev, ptr_a);
124        assert_eq!(cell.get(), Some(ptr_b));
125    }
126
127    #[test]
128    fn clear_returns_previous() {
129        let cell = ContextCell::<u32>::new();
130        let mut a: u32 = 42;
131        let ptr_a = &mut a as *mut u32;
132
133        cell.replace(ptr_a);
134        let prev = cell.clear();
135        assert_eq!(prev, ptr_a);
136        assert!(cell.get().is_none());
137    }
138
139    #[test]
140    fn restore_sets_pointer() {
141        let cell = ContextCell::<u32>::new();
142        let mut a: u32 = 10;
143        let ptr_a = &mut a as *mut u32;
144
145        cell.restore(ptr_a);
146        assert_eq!(cell.get(), Some(ptr_a));
147    }
148
149    #[test]
150    fn deref_mut_returns_reference() {
151        let cell = ContextCell::<u32>::new();
152        let mut a: u32 = 99;
153        let ptr_a = &mut a as *mut u32;
154        cell.replace(ptr_a);
155
156        let r = unsafe { cell.deref_mut() }.unwrap();
157        assert_eq!(*r, 99);
158        *r = 100;
159        assert_eq!(a, 100);
160    }
161
162    #[test]
163    fn deref_mut_none_when_null() {
164        let cell = ContextCell::<u32>::new();
165        assert!(unsafe { cell.deref_mut() }.is_none());
166    }
167
168    #[test]
169    fn default_is_null() {
170        let cell = ContextCell::<u32>::default();
171        assert!(cell.get().is_none());
172    }
173}