Skip to main content

qubit_function/tester/
box_tester.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10// qubit-style: allow explicit-imports
11//! Defines the `BoxTester` public type.
12
13use std::ops::Not;
14
15use super::{
16    Rc,
17    RcTester,
18    Tester,
19};
20
21// ============================================================================
22// BoxTester: Single Ownership Implementation
23// ============================================================================
24
25/// Single ownership Tester implemented using `Box`
26///
27/// `BoxTester` wraps a closure in `Box<dyn Fn() -> bool>`, providing single
28/// ownership semantics with no additional allocation overhead beyond the
29/// initial boxing.
30///
31/// # Characteristics
32///
33/// - **Single ownership**: Cannot be cloned
34/// - **Zero overhead**: Single heap allocation
35/// - **Consuming combination**: `and()`/`or()` consume `self`
36/// - **Type flexibility**: Accepts any `Tester` implementation
37///
38/// # Use Cases
39///
40/// - One-time testing scenarios
41/// - Builder patterns requiring ownership transfer
42/// - Simple state checking without sharing
43/// - Chained calls with ownership transfer
44///
45/// # Examples
46///
47/// ```rust
48/// use qubit_function::{BoxTester, Tester};
49/// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
50///
51/// // State managed externally
52/// let count = Arc::new(AtomicUsize::new(0));
53/// let count_clone = Arc::clone(&count);
54///
55/// let tester = BoxTester::new(move || {
56///     count_clone.load(Ordering::Relaxed) < 3
57/// });
58///
59/// assert!(tester.test());
60/// count.fetch_add(1, Ordering::Relaxed);
61/// assert!(tester.test());
62/// count.fetch_add(1, Ordering::Relaxed);
63/// assert!(tester.test());
64/// count.fetch_add(1, Ordering::Relaxed);
65/// assert!(!tester.test());
66///
67/// // Logical combination
68/// let combined = BoxTester::new(|| true)
69///     .and(|| false)
70///     .or(|| true);
71/// assert!(combined.test());
72/// ```
73///
74pub struct BoxTester {
75    pub(super) function: Box<dyn Fn() -> bool>,
76}
77
78impl BoxTester {
79    /// Creates a new `BoxTester` from a closure
80    ///
81    /// # Type Parameters
82    ///
83    /// * `F` - Closure type implementing `Fn() -> bool`
84    ///
85    /// # Parameters
86    ///
87    /// * `f` - The closure to wrap
88    ///
89    /// # Return Value
90    ///
91    /// A new `BoxTester` instance
92    ///
93    /// # Examples
94    ///
95    /// ```rust
96    /// use qubit_function::BoxTester;
97    ///
98    /// let tester = BoxTester::new(|| true);
99    /// ```
100    #[inline]
101    pub fn new<F>(f: F) -> Self
102    where
103        F: Fn() -> bool + 'static,
104    {
105        BoxTester {
106            function: Box::new(f),
107        }
108    }
109
110    /// Combines this tester with another tester using logical AND
111    ///
112    /// Returns a new `BoxTester` that returns `true` only when both tests
113    /// pass. Short-circuit evaluation: if the first test fails, the second
114    /// will not be executed.
115    ///
116    /// # Type Parameters
117    ///
118    /// * `T` - Type implementing `Tester`
119    ///
120    /// # Parameters
121    ///
122    /// * `next` - The tester to combine with
123    ///
124    /// # Return Value
125    ///
126    /// A new `BoxTester` representing logical AND
127    ///
128    /// # Examples
129    ///
130    /// ```rust
131    /// use qubit_function::{BoxTester, Tester};
132    /// use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering}};
133    ///
134    /// // Simulate service status
135    /// let request_count = Arc::new(AtomicUsize::new(0));
136    /// let is_available = Arc::new(AtomicBool::new(true));
137    /// let max_requests = 1000;
138    ///
139    /// let count_clone = Arc::clone(&request_count);
140    /// let available_clone = Arc::clone(&is_available);
141    ///
142    /// // Service available and request count not exceeded
143    /// let service_ok = BoxTester::new(move || {
144    ///     available_clone.load(Ordering::Relaxed)
145    /// })
146    /// .and(move || {
147    ///     count_clone.load(Ordering::Relaxed) < max_requests
148    /// });
149    ///
150    /// // Initial state: available and request count 0
151    /// assert!(service_ok.test());
152    ///
153    /// // Simulate request increase
154    /// request_count.store(500, Ordering::Relaxed);
155    /// assert!(service_ok.test());
156    ///
157    /// // Request count exceeded
158    /// request_count.store(1500, Ordering::Relaxed);
159    /// assert!(!service_ok.test());
160    ///
161    /// // Service unavailable
162    /// is_available.store(false, Ordering::Relaxed);
163    /// assert!(!service_ok.test());
164    /// ```
165    #[inline]
166    pub fn and<T>(self, next: T) -> BoxTester
167    where
168        T: Tester + 'static,
169    {
170        let self_fn = self.function;
171        let next_tester = next;
172        BoxTester::new(move || self_fn() && next_tester.test())
173    }
174
175    /// Combines this tester with another tester using logical OR
176    ///
177    /// Returns a new `BoxTester` that returns `true` if either test passes.
178    /// Short-circuit evaluation: if the first test passes, the second will
179    /// not be executed.
180    ///
181    /// # Type Parameters
182    ///
183    /// * `T` - Type implementing `Tester`
184    ///
185    /// # Parameters
186    ///
187    /// * `next` - The tester to combine with
188    ///
189    /// # Return Value
190    ///
191    /// A new `BoxTester` representing logical OR
192    ///
193    /// # Examples
194    ///
195    /// ```rust
196    /// use qubit_function::{BoxTester, Tester};
197    /// use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering}};
198    ///
199    /// // Simulate service status
200    /// let request_count = Arc::new(AtomicUsize::new(0));
201    /// let is_healthy = Arc::new(AtomicBool::new(true));
202    /// let max_requests = 100;
203    ///
204    /// let count_clone = Arc::clone(&request_count);
205    /// let health_clone = Arc::clone(&is_healthy);
206    ///
207    /// // Service healthy or low request count
208    /// let can_serve = BoxTester::new(move || {
209    ///     health_clone.load(Ordering::Relaxed)
210    /// })
211    /// .or(move || {
212    ///     count_clone.load(Ordering::Relaxed) < max_requests
213    /// });
214    ///
215    /// // Initial state: healthy and request count 0
216    /// assert!(can_serve.test());
217    ///
218    /// // Request count increased but within limit
219    /// request_count.store(50, Ordering::Relaxed);
220    /// assert!(can_serve.test());
221    ///
222    /// // Request count exceeded but service healthy
223    /// request_count.store(150, Ordering::Relaxed);
224    /// assert!(can_serve.test()); // still healthy
225    ///
226    /// // Service unhealthy but low request count
227    /// is_healthy.store(false, Ordering::Relaxed);
228    /// request_count.store(50, Ordering::Relaxed);
229    /// assert!(can_serve.test()); // low request count
230    ///
231    /// // Unhealthy and high request count
232    /// request_count.store(150, Ordering::Relaxed);
233    /// assert!(!can_serve.test());
234    /// ```
235    #[inline]
236    pub fn or<T>(self, next: T) -> BoxTester
237    where
238        T: Tester + 'static,
239    {
240        let self_fn = self.function;
241        let next_tester = next;
242        BoxTester::new(move || self_fn() || next_tester.test())
243    }
244
245    /// Combines this tester with another tester using logical NAND
246    ///
247    /// Returns a new `BoxTester` that returns `true` unless both tests pass.
248    /// Equivalent to `!(self AND other)`.
249    ///
250    /// # Type Parameters
251    ///
252    /// * `T` - Type implementing `Tester`
253    ///
254    /// # Parameters
255    ///
256    /// * `next` - The tester to combine with
257    ///
258    /// # Return Value
259    ///
260    /// A new `BoxTester` representing logical NAND
261    ///
262    /// # Examples
263    ///
264    /// ```rust
265    /// use qubit_function::{BoxTester, Tester};
266    /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
267    ///
268    /// let flag1 = Arc::new(AtomicBool::new(true));
269    /// let flag2 = Arc::new(AtomicBool::new(true));
270    ///
271    /// let flag1_clone = Arc::clone(&flag1);
272    /// let flag2_clone = Arc::clone(&flag2);
273    ///
274    /// let nand = BoxTester::new(move || {
275    ///     flag1_clone.load(Ordering::Relaxed)
276    /// })
277    /// .nand(move || {
278    ///     flag2_clone.load(Ordering::Relaxed)
279    /// });
280    ///
281    /// // Both true returns false
282    /// assert!(!nand.test());
283    ///
284    /// // At least one false returns true
285    /// flag1.store(false, Ordering::Relaxed);
286    /// assert!(nand.test());
287    /// ```
288    #[inline]
289    pub fn nand<T>(self, next: T) -> BoxTester
290    where
291        T: Tester + 'static,
292    {
293        let self_fn = self.function;
294        let next_tester = next;
295        BoxTester::new(move || !(self_fn() && next_tester.test()))
296    }
297
298    /// Combines this tester with another tester using logical XOR
299    ///
300    /// Returns a new `BoxTester` that returns `true` if exactly one test
301    /// passes.
302    ///
303    /// # Type Parameters
304    ///
305    /// * `T` - Type implementing `Tester`
306    ///
307    /// # Parameters
308    ///
309    /// * `next` - The tester to combine with
310    ///
311    /// # Return Value
312    ///
313    /// A new `BoxTester` representing logical XOR
314    ///
315    /// # Examples
316    ///
317    /// ```rust
318    /// use qubit_function::{BoxTester, Tester};
319    /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
320    ///
321    /// let flag1 = Arc::new(AtomicBool::new(true));
322    /// let flag2 = Arc::new(AtomicBool::new(false));
323    ///
324    /// let flag1_clone1 = Arc::clone(&flag1);
325    /// let flag2_clone1 = Arc::clone(&flag2);
326    ///
327    /// let xor = BoxTester::new(move || {
328    ///     flag1_clone1.load(Ordering::Relaxed)
329    /// })
330    /// .xor(move || {
331    ///     flag2_clone1.load(Ordering::Relaxed)
332    /// });
333    ///
334    /// // One true one false returns true
335    /// assert!(xor.test());
336    ///
337    /// // Both true returns false
338    /// flag2.store(true, Ordering::Relaxed);
339    /// assert!(!xor.test());
340    ///
341    /// // Both false returns false
342    /// flag1.store(false, Ordering::Relaxed);
343    /// flag2.store(false, Ordering::Relaxed);
344    /// assert!(!xor.test());
345    /// ```
346    #[inline]
347    pub fn xor<T>(self, next: T) -> BoxTester
348    where
349        T: Tester + 'static,
350    {
351        let self_fn = self.function;
352        let next_tester = next;
353        BoxTester::new(move || self_fn() ^ next_tester.test())
354    }
355
356    /// Combines this tester with another tester using logical NOR
357    ///
358    /// Returns a new `BoxTester` that returns `true` only when both tests
359    /// fail. Equivalent to `!(self OR other)`.
360    ///
361    /// # Type Parameters
362    ///
363    /// * `T` - Type implementing `Tester`
364    ///
365    /// # Parameters
366    ///
367    /// * `next` - The tester to combine with
368    ///
369    /// # Return Value
370    ///
371    /// A new `BoxTester` representing logical NOR
372    ///
373    /// # Examples
374    ///
375    /// ```rust
376    /// use qubit_function::{BoxTester, Tester};
377    /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
378    ///
379    /// let flag1 = Arc::new(AtomicBool::new(false));
380    /// let flag2 = Arc::new(AtomicBool::new(false));
381    ///
382    /// let flag1_clone = Arc::clone(&flag1);
383    /// let flag2_clone = Arc::clone(&flag2);
384    ///
385    /// let nor = BoxTester::new(move || {
386    ///     flag1_clone.load(Ordering::Relaxed)
387    /// })
388    /// .nor(move || {
389    ///     flag2_clone.load(Ordering::Relaxed)
390    /// });
391    ///
392    /// // Both false returns true
393    /// assert!(nor.test());
394    ///
395    /// // At least one true returns false
396    /// flag1.store(true, Ordering::Relaxed);
397    /// assert!(!nor.test());
398    /// ```
399    #[inline]
400    pub fn nor<T>(self, next: T) -> BoxTester
401    where
402        T: Tester + 'static,
403    {
404        let self_fn = self.function;
405        let next_tester = next;
406        BoxTester::new(move || !(self_fn() || next_tester.test()))
407    }
408}
409
410impl Not for BoxTester {
411    type Output = BoxTester;
412
413    #[inline]
414    fn not(self) -> Self::Output {
415        let self_fn = self.function;
416        BoxTester::new(move || !self_fn())
417    }
418}
419
420impl Tester for BoxTester {
421    #[inline]
422    fn test(&self) -> bool {
423        (self.function)()
424    }
425
426    #[inline]
427    fn into_box(self) -> BoxTester {
428        self
429    }
430
431    #[inline]
432    fn into_rc(self) -> RcTester {
433        let func = self.function;
434        RcTester {
435            function: Rc::new(func),
436        }
437    }
438
439    // Note: BoxTester does not implement Send + Sync, so into_arc()
440    // cannot be implemented. Calling into_arc() on BoxTester will result
441    // in a compile error due to the Send + Sync trait bounds not being
442    // satisfied. The default Tester trait implementation will be used.
443
444    #[inline]
445    fn into_fn(self) -> impl Fn() -> bool {
446        self.function
447    }
448
449    // Note: BoxTester does not implement Clone, so to_box(), to_rc(),
450    // to_arc(), and to_fn() cannot be implemented. Calling these methods
451    // on BoxTester will result in a compile error due to the Clone trait
452    // bound not being satisfied. The default Tester trait implementations
453    // will be used.
454}