quick_bool/lib.rs
1use std::sync::atomic::{AtomicU8, Ordering};
2
3/// A lock-free boolean implementation using atomic operations.
4///
5/// This type represents a 3-way boolean state:
6/// - `Unset`: The value hasn't been evaluated yet
7/// - `True`: The value is true
8/// - `False`: The value is false
9///
10/// Once set to true or false, the value cannot be changed, making it
11/// effectively immutable after initialization.
12#[derive(Debug)]
13pub struct QuickBool {
14 state: AtomicU8,
15}
16
17impl QuickBool {
18 /// Represents the unset state
19 const UNSET: u8 = 0;
20 /// Represents the false state
21 const FALSE: u8 = 0xFF; // Use 0xFF for better branch prediction
22 /// Represents the true state
23 const TRUE: u8 = 1; // Use 1 for true (common case)
24
25 /// Creates a new `QuickBool` in the unset state.
26 pub const fn new() -> Self {
27 Self {
28 state: AtomicU8::new(Self::UNSET),
29 }
30 }
31
32 /// Gets the current value, evaluating the closure if the value is unset.
33 ///
34 /// This method is lock-free and will only evaluate the closure once.
35 /// Subsequent calls will return the cached value.
36 ///
37 /// # Arguments
38 ///
39 /// * `f` - A closure that returns a boolean value
40 ///
41 /// # Returns
42 ///
43 /// The boolean value, either from cache or newly computed
44 ///
45 /// # Example
46 ///
47 /// ```
48 /// use quick_bool::QuickBool;
49 ///
50 /// let quick_bool = QuickBool::new();
51 /// let value = quick_bool.get_or_set(|| {
52 /// // This expensive computation only happens once
53 /// std::thread::sleep(std::time::Duration::from_millis(100));
54 /// true
55 /// });
56 ///
57 /// // Second call returns immediately without computation
58 /// let cached_value = quick_bool.get_or_set(|| panic!("This won't execute"));
59 /// assert_eq!(value, cached_value);
60 /// ```
61 pub fn get_or_set<F>(&self, f: F) -> bool
62 where
63 F: FnOnce() -> bool,
64 {
65 // Fast path: try relaxed load first for better performance
66 let current = self.state.load(Ordering::Relaxed);
67
68 // Optimize match order: TRUE first (most common), then FALSE, then UNSET
69 match current {
70 Self::TRUE => true,
71 Self::FALSE => false,
72 Self::UNSET => {
73 // Value is unset, we need to compute it
74 let computed_value = f();
75 let target_state = if computed_value { Self::TRUE } else { Self::FALSE };
76
77 // Try to set the value atomically
78 match self.state.compare_exchange(
79 Self::UNSET,
80 target_state,
81 Ordering::AcqRel,
82 Ordering::Acquire,
83 ) {
84 Ok(_) => computed_value,
85 Err(actual) => {
86 // Another thread set the value while we were computing
87 // Return the value that was set
88 actual == Self::TRUE
89 }
90 }
91 }
92 _ => unreachable!("Invalid state value"),
93 }
94 }
95
96
97
98
99
100
101
102
103
104 /// Gets the current value without computing it.
105 ///
106 /// Returns `None` if the value is unset, `Some(true)` if true,
107 /// or `Some(false)` if false.
108 ///
109 /// # Example
110 ///
111 /// ```
112 /// use quick_bool::QuickBool;
113 ///
114 /// let quick_bool = QuickBool::new();
115 /// assert_eq!(quick_bool.get(), None);
116 ///
117 /// quick_bool.get_or_set(|| true);
118 /// assert_eq!(quick_bool.get(), Some(true));
119 /// ```
120 #[inline]
121 pub fn get(&self) -> Option<bool> {
122 // Use relaxed ordering since we're only reading
123 match self.state.load(Ordering::Relaxed) {
124 Self::TRUE => Some(true),
125 Self::FALSE => Some(false),
126 Self::UNSET => None,
127 _ => unreachable!("Invalid state value"),
128 }
129 }
130
131 /// Checks if the value has been set.
132 ///
133 /// Returns `true` if the value is either true or false,
134 /// `false` if it's still unset.
135 ///
136 /// # Example
137 ///
138 /// ```
139 /// use quick_bool::QuickBool;
140 ///
141 /// let quick_bool = QuickBool::new();
142 /// assert!(!quick_bool.is_set());
143 ///
144 /// quick_bool.get_or_set(|| true);
145 /// assert!(quick_bool.is_set());
146 /// ```
147 #[inline]
148 pub fn is_set(&self) -> bool {
149 // Use relaxed ordering since we're only checking state
150 self.state.load(Ordering::Relaxed) != Self::UNSET
151 }
152
153 /// Resets the value back to unset state.
154 ///
155 /// This allows the value to be recomputed on the next call to `get_or_set`.
156 ///
157 /// # Example
158 ///
159 /// ```
160 /// use quick_bool::QuickBool;
161 ///
162 /// let quick_bool = QuickBool::new();
163 /// quick_bool.get_or_set(|| true);
164 /// assert!(quick_bool.is_set());
165 ///
166 /// quick_bool.reset();
167 /// assert!(!quick_bool.is_set());
168 /// ```
169 pub fn reset(&self) {
170 // Use Release ordering since we're modifying state
171 self.state.store(Self::UNSET, Ordering::Release);
172 }
173}
174
175impl Default for QuickBool {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181impl Clone for QuickBool {
182 fn clone(&self) -> Self {
183 Self {
184 state: AtomicU8::new(self.state.load(Ordering::Relaxed)),
185 }
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use std::sync::Arc;
193 use std::thread;
194
195 #[test]
196 fn test_new_quick_bool() {
197 let qb = QuickBool::new();
198 assert_eq!(qb.get(), None);
199 assert!(!qb.is_set());
200 }
201
202 #[test]
203 fn test_get_or_set_once() {
204 let qb = QuickBool::new();
205 let value = qb.get_or_set(|| true);
206 assert!(value);
207 assert_eq!(qb.get(), Some(true));
208 assert!(qb.is_set());
209 }
210
211 #[test]
212 fn test_get_or_set_multiple_calls() {
213 let qb = QuickBool::new();
214 let mut call_count = 0;
215
216 let value1 = qb.get_or_set(|| {
217 call_count += 1;
218 false
219 });
220
221 let value2 = qb.get_or_set(|| {
222 call_count += 1;
223 panic!("This should not execute");
224 });
225
226 assert!(!value1);
227 assert!(!value2);
228 assert_eq!(call_count, 1);
229 assert_eq!(qb.get(), Some(false));
230 }
231
232 #[test]
233 fn test_reset() {
234 let qb = QuickBool::new();
235 qb.get_or_set(|| true);
236 assert!(qb.is_set());
237
238 qb.reset();
239 assert!(!qb.is_set());
240 assert_eq!(qb.get(), None);
241 }
242
243 #[test]
244 fn test_concurrent_access() {
245 let qb = Arc::new(QuickBool::new());
246 let mut handles = vec![];
247
248 // Spawn multiple threads that all try to set the value
249 for _ in 0..10 {
250 let qb_clone = Arc::clone(&qb);
251 let handle = thread::spawn(move || {
252 qb_clone.get_or_set(|| {
253 // Simulate some computation
254 thread::sleep(std::time::Duration::from_millis(1));
255 true
256 })
257 });
258 handles.push(handle);
259 }
260
261 // Wait for all threads to complete
262 let results: Vec<bool> = handles.into_iter()
263 .map(|h| h.join().unwrap())
264 .collect();
265
266 // All threads should get the same result
267 assert!(results.iter().all(|&x| x));
268 assert_eq!(qb.get(), Some(true));
269 }
270
271 #[test]
272 fn test_clone() {
273 let qb1 = QuickBool::new();
274 qb1.get_or_set(|| true);
275
276 let qb2 = qb1.clone();
277 assert_eq!(qb2.get(), Some(true));
278 assert!(qb2.is_set());
279 }
280}