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}