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