qubit_function/consumers/bi_consumer_once.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026.
4 * Haixing Hu, Qubit Co. Ltd.
5 *
6 * All rights reserved.
7 *
8 ******************************************************************************/
9//! # BiConsumerOnce Types
10//!
11//! Provides one-time bi-consumer interface implementations for operations
12//! accepting two input parameters without returning a result.
13//!
14//! It is similar to the `FnOnce(&T, &U)` trait in the standard library.
15//!
16//! This module provides a unified `BiConsumerOnce` trait and one concrete
17//! implementation:
18//!
19//! - **`BoxBiConsumerOnce<T, U>`**: Box-based single ownership
20//! implementation
21//!
22//! # Why No Arc/Rc Variants?
23//!
24//! Unlike `BiConsumer` and `ReadonlyBiConsumer`, this module does **not**
25//! provide `ArcBiConsumerOnce` or `RcBiConsumerOnce` implementations. This
26//! is a design decision based on the fundamental incompatibility between
27//! `FnOnce` semantics and shared ownership. See the design documentation
28//! for details.
29//!
30//! # Design Philosophy
31//!
32//! BiConsumerOnce uses `FnOnce(&T, &U)` semantics: for truly one-time
33//! consumption operations.
34//!
35//! Unlike BiConsumer, BiConsumerOnce consumes itself on first call. Suitable
36//! for initialization callbacks, cleanup callbacks, etc.
37//!
38//! # Author
39//!
40//! Haixing Hu
41use crate::{
42 consumers::macros::{
43 impl_box_conditional_consumer,
44 impl_box_consumer_methods,
45 impl_conditional_consumer_debug_display,
46 impl_consumer_common_methods,
47 impl_consumer_debug_display,
48 },
49 macros::{
50 impl_box_once_conversions,
51 impl_closure_once_trait,
52 },
53 predicates::bi_predicate::{
54 BiPredicate,
55 BoxBiPredicate,
56 },
57};
58
59// ==========================================================================
60// Type Aliases
61// ==========================================================================
62
63/// Type alias for bi-consumer once function signature.
64type BiConsumerOnceFn<T, U> = dyn FnOnce(&T, &U);
65
66// =======================================================================
67// 1. BiConsumerOnce Trait - Unified Interface
68// =======================================================================
69
70/// BiConsumerOnce trait - Unified one-time bi-consumer interface
71///
72/// It is similar to the `FnOnce(&T, &U)` trait in the standard library.
73///
74/// Defines core behavior for all one-time bi-consumer types. Similar to a
75/// bi-consumer implementing `FnOnce(&T, &U)`, performs operations
76/// accepting two value references but returning no result (side effects
77/// only), consuming itself in the process.
78///
79/// # Automatic Implementations
80///
81/// - All closures implementing `FnOnce(&T, &U)`
82/// - `BoxBiConsumerOnce<T, U>`
83///
84/// # Features
85///
86/// - **Unified Interface**: All bi-consumer types share the same `accept`
87/// method signature
88/// - **Automatic Implementation**: Closures automatically implement this
89/// trait with zero overhead
90/// - **Type Conversions**: Can convert to BoxBiConsumerOnce
91/// - **Generic Programming**: Write functions accepting any one-time
92/// bi-consumer type
93///
94/// # Examples
95///
96/// ```rust
97/// use qubit_function::{BiConsumerOnce, BoxBiConsumerOnce};
98/// use std::sync::{Arc, Mutex};
99///
100/// fn apply_consumer<C: BiConsumerOnce<i32, i32>>(
101/// consumer: C,
102/// a: &i32,
103/// b: &i32
104/// ) {
105/// consumer.accept(a, b);
106/// }
107///
108/// let log = Arc::new(Mutex::new(Vec::new()));
109/// let l = log.clone();
110/// let box_con = BoxBiConsumerOnce::new(move |x: &i32, y: &i32| {
111/// l.lock().unwrap().push(*x + *y);
112/// });
113/// apply_consumer(box_con, &5, &3);
114/// assert_eq!(*log.lock().unwrap(), vec![8]);
115/// ```
116///
117/// # Author
118///
119/// Haixing Hu
120pub trait BiConsumerOnce<T, U> {
121 /// Performs the one-time consumption operation
122 ///
123 /// Executes an operation on the given two references. The operation
124 /// typically reads input values or produces side effects, but does not
125 /// modify the input values themselves. Consumes self.
126 ///
127 /// # Parameters
128 ///
129 /// * `first` - Reference to the first value to consume
130 /// * `second` - Reference to the second value to consume
131 ///
132 /// # Examples
133 ///
134 /// ```rust
135 /// use qubit_function::{BiConsumerOnce, BoxBiConsumerOnce};
136 ///
137 /// let consumer = BoxBiConsumerOnce::new(|x: &i32, y: &i32| {
138 /// println!("Sum: {}", x + y);
139 /// });
140 /// consumer.accept(&5, &3);
141 /// ```
142 fn accept(self, first: &T, second: &U);
143
144 /// Converts to BoxBiConsumerOnce
145 ///
146 /// **⚠️ Consumes `self`**: Original consumer becomes unavailable after
147 /// calling this method.
148 ///
149 /// # Returns
150 ///
151 /// Returns the wrapped `BoxBiConsumerOnce<T, U>`
152 ///
153 /// # Examples
154 ///
155 /// ```rust
156 /// use qubit_function::BiConsumerOnce;
157 /// use std::sync::{Arc, Mutex};
158 ///
159 /// let log = Arc::new(Mutex::new(Vec::new()));
160 /// let l = log.clone();
161 /// let closure = move |x: &i32, y: &i32| {
162 /// l.lock().unwrap().push(*x + *y);
163 /// };
164 /// let box_consumer = closure.into_box();
165 /// box_consumer.accept(&5, &3);
166 /// assert_eq!(*log.lock().unwrap(), vec![8]);
167 /// ```
168 fn into_box(self) -> BoxBiConsumerOnce<T, U>
169 where
170 Self: Sized + 'static,
171 T: 'static,
172 U: 'static,
173 {
174 BoxBiConsumerOnce::new(move |t, u| self.accept(t, u))
175 }
176
177 /// Converts to a closure
178 ///
179 /// **⚠️ Consumes `self`**: Original consumer becomes unavailable after
180 /// calling this method.
181 ///
182 /// Converts the one-time bi-consumer to a closure usable with standard
183 /// library methods requiring `FnOnce`.
184 ///
185 /// # Returns
186 ///
187 /// Returns a closure implementing `FnOnce(&T, &U)`
188 fn into_fn(self) -> impl FnOnce(&T, &U)
189 where
190 Self: Sized + 'static,
191 {
192 move |t, u| self.accept(t, u)
193 }
194
195 /// Convert to BoxBiConsumerOnce without consuming self
196 ///
197 /// **⚠️ Requires Clone**: This method requires `Self` to implement
198 /// `Clone`. Clones the current bi-consumer and then converts the clone
199 /// to a `BoxBiConsumerOnce`.
200 ///
201 /// # Returns
202 ///
203 /// Returns the wrapped `BoxBiConsumerOnce<T, U>`
204 ///
205 /// # Examples
206 ///
207 /// ```rust
208 /// use qubit_function::BiConsumerOnce;
209 /// use std::sync::{Arc, Mutex};
210 ///
211 /// let log = Arc::new(Mutex::new(Vec::new()));
212 /// let l = log.clone();
213 /// let closure = move |x: &i32, y: &i32| {
214 /// l.lock().unwrap().push(*x + *y);
215 /// };
216 /// let box_consumer = closure.to_box();
217 /// box_consumer.accept(&5, &3);
218 /// assert_eq!(*log.lock().unwrap(), vec![8]);
219 /// ```
220 fn to_box(&self) -> BoxBiConsumerOnce<T, U>
221 where
222 Self: Sized + Clone + 'static,
223 T: 'static,
224 U: 'static,
225 {
226 self.clone().into_box()
227 }
228
229 /// Convert to closure without consuming self
230 ///
231 /// **⚠️ Requires Clone**: This method requires `Self` to implement
232 /// `Clone`. Clones the current bi-consumer and then converts the clone
233 /// to a closure.
234 ///
235 /// # Returns
236 ///
237 /// Returns a closure implementing `FnOnce(&T, &U)`
238 ///
239 /// # Examples
240 ///
241 /// ```rust
242 /// use qubit_function::BiConsumerOnce;
243 /// use std::sync::{Arc, Mutex};
244 ///
245 /// let log = Arc::new(Mutex::new(Vec::new()));
246 /// let l = log.clone();
247 /// let closure = move |x: &i32, y: &i32| {
248 /// l.lock().unwrap().push(*x + *y);
249 /// };
250 /// let func = closure.to_fn();
251 /// func(&5, &3);
252 /// assert_eq!(*log.lock().unwrap(), vec![8]);
253 /// ```
254 fn to_fn(&self) -> impl FnOnce(&T, &U)
255 where
256 Self: Sized + Clone + 'static,
257 {
258 self.clone().into_fn()
259 }
260}
261
262// =======================================================================
263// 2. BoxBiConsumerOnce - Single Ownership Implementation
264// =======================================================================
265
266/// BoxBiConsumerOnce struct
267///
268/// A one-time bi-consumer implementation based on
269/// `Box<dyn FnOnce(&T, &U)>` for single ownership scenarios. This is the
270/// simplest one-time bi-consumer type for truly one-time use.
271///
272/// # Features
273///
274/// - **Single Ownership**: Not cloneable, ownership moves on use
275/// - **Zero Overhead**: No reference counting or locking
276/// - **One-Time Use**: Consumes self on first call
277/// - **Builder Pattern**: Method chaining consumes `self` naturally
278///
279/// # Use Cases
280///
281/// Choose `BoxBiConsumerOnce` when:
282/// - The bi-consumer is truly used only once
283/// - Building pipelines where ownership naturally flows
284/// - The consumer captures values that should be consumed
285/// - Performance is critical and sharing overhead is unacceptable
286///
287/// # Performance
288///
289/// `BoxBiConsumerOnce` has the best performance:
290/// - No reference counting overhead
291/// - No lock acquisition or runtime borrow checking
292/// - Direct function call through vtable
293/// - Minimal memory footprint (single pointer)
294///
295/// # Examples
296///
297/// ```rust
298/// use qubit_function::{BiConsumerOnce, BoxBiConsumerOnce};
299///
300/// let consumer = BoxBiConsumerOnce::new(|x: &i32, y: &i32| {
301/// println!("Sum: {}", x + y);
302/// });
303/// consumer.accept(&5, &3);
304/// ```
305///
306/// # Author
307///
308/// Haixing Hu
309pub struct BoxBiConsumerOnce<T, U> {
310 function: Box<BiConsumerOnceFn<T, U>>,
311 name: Option<String>,
312}
313
314// All methods require T: 'static and U: 'static because
315// Box<dyn FnOnce(&T, &U)> requires it
316impl<T, U> BoxBiConsumerOnce<T, U> {
317 // Generates: new(), new_with_name(), name(), set_name(), noop()
318 impl_consumer_common_methods!(
319 BoxBiConsumerOnce<T, U>,
320 (FnOnce(&T, &U) + 'static),
321 |f| Box::new(f)
322 );
323
324 // Generates: when() and and_then() methods that consume self
325 impl_box_consumer_methods!(
326 BoxBiConsumerOnce<T, U>,
327 BoxConditionalBiConsumerOnce,
328 BiConsumerOnce
329 );
330}
331
332impl<T, U> BiConsumerOnce<T, U> for BoxBiConsumerOnce<T, U> {
333 fn accept(self, first: &T, second: &U) {
334 (self.function)(first, second)
335 }
336
337 impl_box_once_conversions!(
338 BoxBiConsumerOnce<T, U>,
339 BiConsumerOnce,
340 FnOnce(&T, &U)
341 );
342}
343
344// Use macro to generate Debug and Display implementations
345impl_consumer_debug_display!(BoxBiConsumerOnce<T, U>);
346
347// =======================================================================
348// 3. Implement BiConsumerOnce trait for closures
349// =======================================================================
350
351// Implement BiConsumerOnce for all FnOnce(&T, &U) using macro
352impl_closure_once_trait!(
353 BiConsumerOnce<T, U>,
354 accept,
355 BoxBiConsumerOnce,
356 FnOnce(first: &T, second: &U)
357);
358
359// =======================================================================
360// 4. Provide extension methods for closures
361// =======================================================================
362
363/// Extension trait providing one-time bi-consumer composition methods for
364/// closures
365///
366/// Provides `and_then` and other composition methods for all closures
367/// implementing `FnOnce(&T, &U)`, enabling direct method chaining on
368/// closures without explicit wrapper types.
369///
370/// # Features
371///
372/// - **Natural Syntax**: Chain operations directly on closures
373/// - **Returns BoxBiConsumerOnce**: Composition results can be further
374/// chained
375/// - **Zero Cost**: No overhead when composing closures
376/// - **Automatic Implementation**: All `FnOnce(&T, &U)` closures get
377/// these methods automatically
378///
379/// # Examples
380///
381/// ```rust
382/// use qubit_function::{BiConsumerOnce, FnBiConsumerOnceOps};
383/// use std::sync::{Arc, Mutex};
384///
385/// let log = Arc::new(Mutex::new(Vec::new()));
386/// let l1 = log.clone();
387/// let l2 = log.clone();
388/// let chained = (move |x: &i32, y: &i32| {
389/// l1.lock().unwrap().push(*x + *y);
390/// }).and_then(move |x: &i32, y: &i32| {
391/// l2.lock().unwrap().push(*x * *y);
392/// });
393/// chained.accept(&5, &3);
394/// assert_eq!(*log.lock().unwrap(), vec![8, 15]);
395/// ```
396///
397/// # Author
398///
399/// Haixing Hu
400pub trait FnBiConsumerOnceOps<T, U>: FnOnce(&T, &U) + Sized {
401 /// Chains another one-time bi-consumer in sequence
402 ///
403 /// Returns a new consumer executing the current operation first, then
404 /// the next operation. Consumes the current closure and returns
405 /// `BoxBiConsumerOnce<T, U>`.
406 ///
407 /// # Type Parameters
408 ///
409 /// * `C` - The type of the next consumer
410 ///
411 /// # Parameters
412 ///
413 /// * `next` - The consumer to execute after the current operation. **Note:
414 /// This parameter is passed by value and will transfer ownership.** Since
415 /// `BoxBiConsumerOnce` cannot be cloned, the parameter will be consumed.
416 /// Can be:
417 /// - A closure: `|x: &T, y: &U|`
418 /// - A `BoxBiConsumerOnce<T, U>`
419 /// - Any type implementing `BiConsumerOnce<T, U>`
420 ///
421 /// # Returns
422 ///
423 /// Returns the composed `BoxBiConsumerOnce<T, U>`
424 ///
425 /// # Examples
426 ///
427 /// ```rust
428 /// use qubit_function::{BiConsumerOnce, FnBiConsumerOnceOps};
429 /// use std::sync::{Arc, Mutex};
430 ///
431 /// let log = Arc::new(Mutex::new(Vec::new()));
432 /// let l1 = log.clone();
433 /// let l2 = log.clone();
434 /// let chained = (move |x: &i32, y: &i32| {
435 /// l1.lock().unwrap().push(*x + *y);
436 /// }).and_then(move |x: &i32, y: &i32| {
437 /// l2.lock().unwrap().push(*x * *y);
438 /// }).and_then(|x: &i32, y: &i32| {
439 /// println!("Result: {}, {}", x, y);
440 /// });
441 ///
442 /// chained.accept(&5, &3);
443 /// assert_eq!(*log.lock().unwrap(), vec![8, 15]);
444 /// ```
445 fn and_then<C>(self, next: C) -> BoxBiConsumerOnce<T, U>
446 where
447 Self: 'static,
448 C: BiConsumerOnce<T, U> + 'static,
449 T: 'static,
450 U: 'static,
451 {
452 let first = self;
453 let second = next;
454 BoxBiConsumerOnce::new(move |t, u| {
455 first(t, u);
456 second.accept(t, u);
457 })
458 }
459}
460
461/// Implements FnBiConsumerOnceOps for all closure types
462impl<T, U, F> FnBiConsumerOnceOps<T, U> for F where F: FnOnce(&T, &U) {}
463
464// =======================================================================
465// 5. BoxConditionalBiConsumerOnce - Box-based Conditional BiConsumerOnce
466// =======================================================================
467
468/// BoxConditionalBiConsumerOnce struct
469///
470/// A conditional one-time bi-consumer that only executes when a predicate is satisfied.
471/// Uses `BoxBiConsumerOnce` and `BoxBiPredicate` for single ownership semantics.
472///
473/// This type is typically created by calling `BoxBiConsumerOnce::when()` and is
474/// designed to work with the `or_else()` method to create if-then-else logic.
475///
476/// # Features
477///
478/// - **Single Ownership**: Not cloneable, consumes `self` on use
479/// - **Conditional Execution**: Only consumes when predicate returns `true`
480/// - **Chainable**: Can add `or_else` branch to create if-then-else logic
481/// - **Implements BiConsumerOnce**: Can be used anywhere a `BiConsumerOnce` is expected
482///
483/// # Examples
484///
485/// ## Basic Conditional Execution
486///
487/// ```rust
488/// use qubit_function::{BiConsumerOnce, BoxBiConsumerOnce};
489/// use std::sync::{Arc, Mutex};
490///
491/// let log = Arc::new(Mutex::new(Vec::new()));
492/// let l = log.clone();
493/// let consumer = BoxBiConsumerOnce::new(move |x: &i32, y: &i32| {
494/// l.lock().unwrap().push(*x + *y);
495/// });
496/// let conditional = consumer.when(|x: &i32, y: &i32| *x > 0 && *y > 0);
497///
498/// conditional.accept(&5, &3);
499/// assert_eq!(*log.lock().unwrap(), vec![8]); // Executed
500/// ```
501///
502/// ## With or_else Branch
503///
504/// ```rust
505/// use qubit_function::{BiConsumerOnce, BoxBiConsumerOnce};
506/// use std::sync::{Arc, Mutex};
507///
508/// let log = Arc::new(Mutex::new(Vec::new()));
509/// let l1 = log.clone();
510/// let l2 = log.clone();
511/// let consumer = BoxBiConsumerOnce::new(move |x: &i32, y: &i32| {
512/// l1.lock().unwrap().push(*x + *y);
513/// }).when(|x: &i32, y: &i32| *x > 0 && *y > 0)
514/// .or_else(move |x: &i32, y: &i32| {
515/// l2.lock().unwrap().push(*x * *y);
516/// });
517///
518/// consumer.accept(&5, &3);
519/// assert_eq!(*log.lock().unwrap(), vec![8]); // when branch executed
520/// ```
521///
522/// # Author
523///
524/// Haixing Hu
525pub struct BoxConditionalBiConsumerOnce<T, U> {
526 consumer: BoxBiConsumerOnce<T, U>,
527 predicate: BoxBiPredicate<T, U>,
528}
529
530// Generate and_then and or_else methods using macro
531impl_box_conditional_consumer!(
532 BoxConditionalBiConsumerOnce<T, U>,
533 BoxBiConsumerOnce,
534 BiConsumerOnce
535);
536
537impl<T, U> BiConsumerOnce<T, U> for BoxConditionalBiConsumerOnce<T, U> {
538 fn accept(self, first: &T, second: &U) {
539 if self.predicate.test(first, second) {
540 self.consumer.accept(first, second);
541 }
542 }
543
544 fn into_box(self) -> BoxBiConsumerOnce<T, U>
545 where
546 T: 'static,
547 U: 'static,
548 {
549 let pred = self.predicate;
550 let consumer = self.consumer;
551 BoxBiConsumerOnce::new(move |t, u| {
552 if pred.test(t, u) {
553 consumer.accept(t, u);
554 }
555 })
556 }
557
558 fn into_fn(self) -> impl FnOnce(&T, &U) {
559 let pred = self.predicate;
560 let consumer = self.consumer;
561 move |t: &T, u: &U| {
562 if pred.test(t, u) {
563 consumer.accept(t, u);
564 }
565 }
566 }
567}
568
569// Use macro to generate Debug and Display implementations
570impl_conditional_consumer_debug_display!(BoxConditionalBiConsumerOnce<T, U>);