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}