Skip to main content

miden_utils_sync/
once_lock.rs

1use core::fmt;
2#[cfg(feature = "std")]
3use std::sync::OnceLock;
4
5#[cfg(not(feature = "std"))]
6use once_cell::race::OnceBox;
7
8/// A cache-invalidation wrapper with consistent semantics across std and no_std.
9///
10/// | Feature | Backing type |
11/// |---------|-------------|
12/// | `std`   | `std::sync::OnceLock<T>` |
13/// | no_std  | `once_cell::race::OnceBox<T>` |
14///
15/// # Why not `Clone`?
16///
17/// Under `std`, `OnceLock::clone` copies the initialized value.
18/// Under no_std, `OnceBox` does not support value extraction, so a "clone"
19/// would silently produce an empty instance — a violation of the `Clone`
20/// contract. `Clone` is therefore not implemented on either configuration.
21/// If you need a fresh instance, call `OnceLockCompat::new()` explicitly.
22pub struct OnceLockCompat<T> {
23    #[cfg(feature = "std")]
24    inner: OnceLock<T>,
25    #[cfg(not(feature = "std"))]
26    inner: OnceBox<T>,
27}
28
29impl<T> OnceLockCompat<T> {
30    /// Creates a new, empty `OnceLockCompat`.
31    pub const fn new() -> Self {
32        #[cfg(feature = "std")]
33        {
34            Self { inner: OnceLock::new() }
35        }
36        #[cfg(not(feature = "std"))]
37        {
38            Self { inner: OnceBox::new() }
39        }
40    }
41
42    /// Returns the value if already initialized, otherwise `None`.
43    pub fn get(&self) -> Option<&T> {
44        #[cfg(feature = "std")]
45        {
46            self.inner.get()
47        }
48        #[cfg(not(feature = "std"))]
49        {
50            // OnceBox::get() returns Option<&Box<T>>
51            self.inner.get().map(|b| -> &T { b })
52        }
53    }
54
55    /// Returns the value if initialized, or initializes it with `f`.
56    ///
57    /// If multiple threads race, each may execute `f`, but only one value is
58    /// stored; the losers' values are dropped immediately.
59    pub fn get_or_init<F>(&self, f: F) -> &T
60    where
61        F: FnOnce() -> T,
62    {
63        #[cfg(feature = "std")]
64        {
65            self.inner.get_or_init(f)
66        }
67        #[cfg(not(feature = "std"))]
68        {
69            // OnceBox::get_or_init() returns &Box<T>
70            let b: &T = self.inner.get_or_init(|| alloc::boxed::Box::new(f()));
71            b
72        }
73    }
74
75    /// Invalidates the cache so the next [`get_or_init`](OnceLockCompat::get_or_init)
76    /// recomputes the value.
77    ///
78    /// # no_std limitation
79    ///
80    /// Under no_std the backing `OnceBox` does not support extracting its value,
81    /// so the stored value is **dropped** on reset and cannot be recovered.
82    /// If you need the value before invalidating, call [`get`](OnceLockCompat::get) first.
83    pub fn reset(&mut self) {
84        #[cfg(feature = "std")]
85        {
86            self.inner = OnceLock::new();
87        }
88        #[cfg(not(feature = "std"))]
89        {
90            self.inner = OnceBox::new();
91        }
92    }
93}
94
95impl<T> Default for OnceLockCompat<T> {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101impl<T: fmt::Debug> fmt::Debug for OnceLockCompat<T> {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        self.inner.fmt(f)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn new_is_empty() {
113        let lock: OnceLockCompat<u32> = OnceLockCompat::new();
114        assert!(lock.get().is_none());
115    }
116
117    #[test]
118    fn get_or_init_initializes() {
119        let lock: OnceLockCompat<u32> = OnceLockCompat::new();
120        let val = lock.get_or_init(|| 42);
121        assert_eq!(*val, 42);
122        assert_eq!(*lock.get().unwrap(), 42);
123    }
124
125    #[test]
126    fn reset_clears_value() {
127        let mut lock: OnceLockCompat<u32> = OnceLockCompat::new();
128        lock.get_or_init(|| 42);
129        assert!(lock.get().is_some());
130        lock.reset();
131        assert!(lock.get().is_none());
132    }
133
134    #[test]
135    fn get_or_init_after_reset() {
136        let mut lock: OnceLockCompat<u32> = OnceLockCompat::new();
137        lock.get_or_init(|| 1);
138        lock.reset();
139        let val = lock.get_or_init(|| 2);
140        assert_eq!(*val, 2);
141    }
142}