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}