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}