Skip to main content

qubit_function/testers/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    Tester,
18    rc_tester::RcTester,
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 { function: Box::new(f) }
106    }
107
108    /// Combines this tester with another tester using logical AND
109    ///
110    /// Returns a new `BoxTester` that returns `true` only when both tests
111    /// pass. Short-circuit evaluation: if the first test fails, the second
112    /// will not be executed.
113    ///
114    /// # Type Parameters
115    ///
116    /// * `T` - Type implementing `Tester`
117    ///
118    /// # Parameters
119    ///
120    /// * `next` - The tester to combine with
121    ///
122    /// # Return Value
123    ///
124    /// A new `BoxTester` representing logical AND
125    ///
126    /// # Examples
127    ///
128    /// ```rust
129    /// use qubit_function::{BoxTester, Tester};
130    /// use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering}};
131    ///
132    /// // Simulate service status
133    /// let request_count = Arc::new(AtomicUsize::new(0));
134    /// let is_available = Arc::new(AtomicBool::new(true));
135    /// let max_requests = 1000;
136    ///
137    /// let count_clone = Arc::clone(&request_count);
138    /// let available_clone = Arc::clone(&is_available);
139    ///
140    /// // Service available and request count not exceeded
141    /// let service_ok = BoxTester::new(move || {
142    ///     available_clone.load(Ordering::Relaxed)
143    /// })
144    /// .and(move || {
145    ///     count_clone.load(Ordering::Relaxed) < max_requests
146    /// });
147    ///
148    /// // Initial state: available and request count 0
149    /// assert!(service_ok.test());
150    ///
151    /// // Simulate request increase
152    /// request_count.store(500, Ordering::Relaxed);
153    /// assert!(service_ok.test());
154    ///
155    /// // Request count exceeded
156    /// request_count.store(1500, Ordering::Relaxed);
157    /// assert!(!service_ok.test());
158    ///
159    /// // Service unavailable
160    /// is_available.store(false, Ordering::Relaxed);
161    /// assert!(!service_ok.test());
162    /// ```
163    #[inline]
164    pub fn and<T>(self, next: T) -> BoxTester
165    where
166        T: Tester + 'static,
167    {
168        let self_fn = self.function;
169        let next_tester = next;
170        BoxTester::new(move || self_fn() && next_tester.test())
171    }
172
173    /// Combines this tester with another tester using logical OR
174    ///
175    /// Returns a new `BoxTester` that returns `true` if either test passes.
176    /// Short-circuit evaluation: if the first test passes, the second will
177    /// not be executed.
178    ///
179    /// # Type Parameters
180    ///
181    /// * `T` - Type implementing `Tester`
182    ///
183    /// # Parameters
184    ///
185    /// * `next` - The tester to combine with
186    ///
187    /// # Return Value
188    ///
189    /// A new `BoxTester` representing logical OR
190    ///
191    /// # Examples
192    ///
193    /// ```rust
194    /// use qubit_function::{BoxTester, Tester};
195    /// use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering}};
196    ///
197    /// // Simulate service status
198    /// let request_count = Arc::new(AtomicUsize::new(0));
199    /// let is_healthy = Arc::new(AtomicBool::new(true));
200    /// let max_requests = 100;
201    ///
202    /// let count_clone = Arc::clone(&request_count);
203    /// let health_clone = Arc::clone(&is_healthy);
204    ///
205    /// // Service healthy or low request count
206    /// let can_serve = BoxTester::new(move || {
207    ///     health_clone.load(Ordering::Relaxed)
208    /// })
209    /// .or(move || {
210    ///     count_clone.load(Ordering::Relaxed) < max_requests
211    /// });
212    ///
213    /// // Initial state: healthy and request count 0
214    /// assert!(can_serve.test());
215    ///
216    /// // Request count increased but within limit
217    /// request_count.store(50, Ordering::Relaxed);
218    /// assert!(can_serve.test());
219    ///
220    /// // Request count exceeded but service healthy
221    /// request_count.store(150, Ordering::Relaxed);
222    /// assert!(can_serve.test()); // still healthy
223    ///
224    /// // Service unhealthy but low request count
225    /// is_healthy.store(false, Ordering::Relaxed);
226    /// request_count.store(50, Ordering::Relaxed);
227    /// assert!(can_serve.test()); // low request count
228    ///
229    /// // Unhealthy and high request count
230    /// request_count.store(150, Ordering::Relaxed);
231    /// assert!(!can_serve.test());
232    /// ```
233    #[inline]
234    pub fn or<T>(self, next: T) -> BoxTester
235    where
236        T: Tester + 'static,
237    {
238        let self_fn = self.function;
239        let next_tester = next;
240        BoxTester::new(move || self_fn() || next_tester.test())
241    }
242
243    /// Combines this tester with another tester using logical NAND
244    ///
245    /// Returns a new `BoxTester` that returns `true` unless both tests pass.
246    /// Equivalent to `!(self AND other)`.
247    ///
248    /// # Type Parameters
249    ///
250    /// * `T` - Type implementing `Tester`
251    ///
252    /// # Parameters
253    ///
254    /// * `next` - The tester to combine with
255    ///
256    /// # Return Value
257    ///
258    /// A new `BoxTester` representing logical NAND
259    ///
260    /// # Examples
261    ///
262    /// ```rust
263    /// use qubit_function::{BoxTester, Tester};
264    /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
265    ///
266    /// let flag1 = Arc::new(AtomicBool::new(true));
267    /// let flag2 = Arc::new(AtomicBool::new(true));
268    ///
269    /// let flag1_clone = Arc::clone(&flag1);
270    /// let flag2_clone = Arc::clone(&flag2);
271    ///
272    /// let nand = BoxTester::new(move || {
273    ///     flag1_clone.load(Ordering::Relaxed)
274    /// })
275    /// .nand(move || {
276    ///     flag2_clone.load(Ordering::Relaxed)
277    /// });
278    ///
279    /// // Both true returns false
280    /// assert!(!nand.test());
281    ///
282    /// // At least one false returns true
283    /// flag1.store(false, Ordering::Relaxed);
284    /// assert!(nand.test());
285    /// ```
286    #[inline]
287    pub fn nand<T>(self, next: T) -> BoxTester
288    where
289        T: Tester + 'static,
290    {
291        let self_fn = self.function;
292        let next_tester = next;
293        BoxTester::new(move || !(self_fn() && next_tester.test()))
294    }
295
296    /// Combines this tester with another tester using logical XOR
297    ///
298    /// Returns a new `BoxTester` that returns `true` if exactly one test
299    /// passes.
300    ///
301    /// # Type Parameters
302    ///
303    /// * `T` - Type implementing `Tester`
304    ///
305    /// # Parameters
306    ///
307    /// * `next` - The tester to combine with
308    ///
309    /// # Return Value
310    ///
311    /// A new `BoxTester` representing logical XOR
312    ///
313    /// # Examples
314    ///
315    /// ```rust
316    /// use qubit_function::{BoxTester, Tester};
317    /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
318    ///
319    /// let flag1 = Arc::new(AtomicBool::new(true));
320    /// let flag2 = Arc::new(AtomicBool::new(false));
321    ///
322    /// let flag1_clone1 = Arc::clone(&flag1);
323    /// let flag2_clone1 = Arc::clone(&flag2);
324    ///
325    /// let xor = BoxTester::new(move || {
326    ///     flag1_clone1.load(Ordering::Relaxed)
327    /// })
328    /// .xor(move || {
329    ///     flag2_clone1.load(Ordering::Relaxed)
330    /// });
331    ///
332    /// // One true one false returns true
333    /// assert!(xor.test());
334    ///
335    /// // Both true returns false
336    /// flag2.store(true, Ordering::Relaxed);
337    /// assert!(!xor.test());
338    ///
339    /// // Both false returns false
340    /// flag1.store(false, Ordering::Relaxed);
341    /// flag2.store(false, Ordering::Relaxed);
342    /// assert!(!xor.test());
343    /// ```
344    #[inline]
345    pub fn xor<T>(self, next: T) -> BoxTester
346    where
347        T: Tester + 'static,
348    {
349        let self_fn = self.function;
350        let next_tester = next;
351        BoxTester::new(move || self_fn() ^ next_tester.test())
352    }
353
354    /// Combines this tester with another tester using logical NOR
355    ///
356    /// Returns a new `BoxTester` that returns `true` only when both tests
357    /// fail. Equivalent to `!(self OR other)`.
358    ///
359    /// # Type Parameters
360    ///
361    /// * `T` - Type implementing `Tester`
362    ///
363    /// # Parameters
364    ///
365    /// * `next` - The tester to combine with
366    ///
367    /// # Return Value
368    ///
369    /// A new `BoxTester` representing logical NOR
370    ///
371    /// # Examples
372    ///
373    /// ```rust
374    /// use qubit_function::{BoxTester, Tester};
375    /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
376    ///
377    /// let flag1 = Arc::new(AtomicBool::new(false));
378    /// let flag2 = Arc::new(AtomicBool::new(false));
379    ///
380    /// let flag1_clone = Arc::clone(&flag1);
381    /// let flag2_clone = Arc::clone(&flag2);
382    ///
383    /// let nor = BoxTester::new(move || {
384    ///     flag1_clone.load(Ordering::Relaxed)
385    /// })
386    /// .nor(move || {
387    ///     flag2_clone.load(Ordering::Relaxed)
388    /// });
389    ///
390    /// // Both false returns true
391    /// assert!(nor.test());
392    ///
393    /// // At least one true returns false
394    /// flag1.store(true, Ordering::Relaxed);
395    /// assert!(!nor.test());
396    /// ```
397    #[inline]
398    pub fn nor<T>(self, next: T) -> BoxTester
399    where
400        T: Tester + 'static,
401    {
402        let self_fn = self.function;
403        let next_tester = next;
404        BoxTester::new(move || !(self_fn() || next_tester.test()))
405    }
406}
407
408impl Not for BoxTester {
409    type Output = BoxTester;
410
411    #[inline]
412    fn not(self) -> Self::Output {
413        let self_fn = self.function;
414        BoxTester::new(move || !self_fn())
415    }
416}
417
418impl Tester for BoxTester {
419    #[inline]
420    fn test(&self) -> bool {
421        (self.function)()
422    }
423
424    #[inline]
425    fn into_box(self) -> BoxTester {
426        self
427    }
428
429    #[inline]
430    fn into_rc(self) -> RcTester {
431        let func = self.function;
432        RcTester {
433            function: Rc::new(func),
434        }
435    }
436
437    // Note: BoxTester does not implement Send + Sync, so into_arc()
438    // cannot be implemented. Calling into_arc() on BoxTester will result
439    // in a compile error due to the Send + Sync trait bounds not being
440    // satisfied. The default Tester trait implementation will be used.
441
442    #[inline]
443    fn into_fn(self) -> impl Fn() -> bool {
444        self.function
445    }
446
447    // Note: BoxTester does not implement Clone, so to_box(), to_rc(),
448    // to_arc(), and to_fn() cannot be implemented. Calling these methods
449    // on BoxTester will result in a compile error due to the Clone trait
450    // bound not being satisfied. The default Tester trait implementations
451    // will be used.
452}