prism3_function/consumers/
bi_consumer_once.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025.
4 *    3-Prism 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 prism3_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 prism3_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 prism3_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        T: 'static,
192        U: 'static,
193    {
194        move |t, u| self.accept(t, u)
195    }
196
197    /// Convert to BoxBiConsumerOnce without consuming self
198    ///
199    /// **⚠️ Requires Clone**: This method requires `Self` to implement
200    /// `Clone`. Clones the current bi-consumer and then converts the clone
201    /// to a `BoxBiConsumerOnce`.
202    ///
203    /// # Returns
204    ///
205    /// Returns the wrapped `BoxBiConsumerOnce<T, U>`
206    ///
207    /// # Examples
208    ///
209    /// ```rust
210    /// use prism3_function::BiConsumerOnce;
211    /// use std::sync::{Arc, Mutex};
212    ///
213    /// let log = Arc::new(Mutex::new(Vec::new()));
214    /// let l = log.clone();
215    /// let closure = move |x: &i32, y: &i32| {
216    ///     l.lock().unwrap().push(*x + *y);
217    /// };
218    /// let box_consumer = closure.to_box();
219    /// box_consumer.accept(&5, &3);
220    /// assert_eq!(*log.lock().unwrap(), vec![8]);
221    /// ```
222    fn to_box(&self) -> BoxBiConsumerOnce<T, U>
223    where
224        Self: Sized + Clone + 'static,
225        T: 'static,
226        U: 'static,
227    {
228        self.clone().into_box()
229    }
230
231    /// Convert to closure without consuming self
232    ///
233    /// **⚠️ Requires Clone**: This method requires `Self` to implement
234    /// `Clone`. Clones the current bi-consumer and then converts the clone
235    /// to a closure.
236    ///
237    /// # Returns
238    ///
239    /// Returns a closure implementing `FnOnce(&T, &U)`
240    ///
241    /// # Examples
242    ///
243    /// ```rust
244    /// use prism3_function::BiConsumerOnce;
245    /// use std::sync::{Arc, Mutex};
246    ///
247    /// let log = Arc::new(Mutex::new(Vec::new()));
248    /// let l = log.clone();
249    /// let closure = move |x: &i32, y: &i32| {
250    ///     l.lock().unwrap().push(*x + *y);
251    /// };
252    /// let func = closure.to_fn();
253    /// func(&5, &3);
254    /// assert_eq!(*log.lock().unwrap(), vec![8]);
255    /// ```
256    fn to_fn(&self) -> impl FnOnce(&T, &U)
257    where
258        Self: Sized + Clone + 'static,
259        T: 'static,
260        U: 'static,
261    {
262        self.clone().into_fn()
263    }
264}
265
266// =======================================================================
267// 2. BoxBiConsumerOnce - Single Ownership Implementation
268// =======================================================================
269
270/// BoxBiConsumerOnce struct
271///
272/// A one-time bi-consumer implementation based on
273/// `Box<dyn FnOnce(&T, &U)>` for single ownership scenarios. This is the
274/// simplest one-time bi-consumer type for truly one-time use.
275///
276/// # Features
277///
278/// - **Single Ownership**: Not cloneable, ownership moves on use
279/// - **Zero Overhead**: No reference counting or locking
280/// - **One-Time Use**: Consumes self on first call
281/// - **Builder Pattern**: Method chaining consumes `self` naturally
282///
283/// # Use Cases
284///
285/// Choose `BoxBiConsumerOnce` when:
286/// - The bi-consumer is truly used only once
287/// - Building pipelines where ownership naturally flows
288/// - The consumer captures values that should be consumed
289/// - Performance is critical and sharing overhead is unacceptable
290///
291/// # Performance
292///
293/// `BoxBiConsumerOnce` has the best performance:
294/// - No reference counting overhead
295/// - No lock acquisition or runtime borrow checking
296/// - Direct function call through vtable
297/// - Minimal memory footprint (single pointer)
298///
299/// # Examples
300///
301/// ```rust
302/// use prism3_function::{BiConsumerOnce, BoxBiConsumerOnce};
303///
304/// let consumer = BoxBiConsumerOnce::new(|x: &i32, y: &i32| {
305///     println!("Sum: {}", x + y);
306/// });
307/// consumer.accept(&5, &3);
308/// ```
309///
310/// # Author
311///
312/// Haixing Hu
313pub struct BoxBiConsumerOnce<T, U> {
314    function: Box<BiConsumerOnceFn<T, U>>,
315    name: Option<String>,
316}
317
318// All methods require T: 'static and U: 'static because
319// Box<dyn FnOnce(&T, &U)> requires it
320impl<T, U> BoxBiConsumerOnce<T, U>
321where
322    T: 'static,
323    U: 'static,
324{
325    // Generates: new(), new_with_name(), name(), set_name(), noop()
326    impl_consumer_common_methods!(
327        BoxBiConsumerOnce<T, U>,
328        (FnOnce(&T, &U) + 'static),
329        |f| Box::new(f)
330    );
331
332    // Generates: when() and and_then() methods that consume self
333    impl_box_consumer_methods!(
334        BoxBiConsumerOnce<T, U>,
335        BoxConditionalBiConsumerOnce,
336        BiConsumerOnce
337    );
338}
339
340impl<T, U> BiConsumerOnce<T, U> for BoxBiConsumerOnce<T, U> {
341    fn accept(self, first: &T, second: &U) {
342        (self.function)(first, second)
343    }
344
345    impl_box_once_conversions!(
346        BoxBiConsumerOnce<T, U>,
347        BiConsumerOnce,
348        FnOnce(&T, &U)
349    );
350}
351
352// Use macro to generate Debug and Display implementations
353impl_consumer_debug_display!(BoxBiConsumerOnce<T, U>);
354
355// =======================================================================
356// 3. Implement BiConsumerOnce trait for closures
357// =======================================================================
358
359// Implement BiConsumerOnce for all FnOnce(&T, &U) using macro
360impl_closure_once_trait!(
361    BiConsumerOnce<T, U>,
362    accept,
363    BoxBiConsumerOnce,
364    FnOnce(first: &T, second: &U)
365);
366
367// =======================================================================
368// 4. Provide extension methods for closures
369// =======================================================================
370
371/// Extension trait providing one-time bi-consumer composition methods for
372/// closures
373///
374/// Provides `and_then` and other composition methods for all closures
375/// implementing `FnOnce(&T, &U)`, enabling direct method chaining on
376/// closures without explicit wrapper types.
377///
378/// # Features
379///
380/// - **Natural Syntax**: Chain operations directly on closures
381/// - **Returns BoxBiConsumerOnce**: Composition results can be further
382///   chained
383/// - **Zero Cost**: No overhead when composing closures
384/// - **Automatic Implementation**: All `FnOnce(&T, &U)` closures get
385///   these methods automatically
386///
387/// # Examples
388///
389/// ```rust
390/// use prism3_function::{BiConsumerOnce, FnBiConsumerOnceOps};
391/// use std::sync::{Arc, Mutex};
392///
393/// let log = Arc::new(Mutex::new(Vec::new()));
394/// let l1 = log.clone();
395/// let l2 = log.clone();
396/// let chained = (move |x: &i32, y: &i32| {
397///     l1.lock().unwrap().push(*x + *y);
398/// }).and_then(move |x: &i32, y: &i32| {
399///     l2.lock().unwrap().push(*x * *y);
400/// });
401/// chained.accept(&5, &3);
402/// assert_eq!(*log.lock().unwrap(), vec![8, 15]);
403/// ```
404///
405/// # Author
406///
407/// Haixing Hu
408pub trait FnBiConsumerOnceOps<T, U>: FnOnce(&T, &U) + Sized {
409    /// Chains another one-time bi-consumer in sequence
410    ///
411    /// Returns a new consumer executing the current operation first, then
412    /// the next operation. Consumes the current closure and returns
413    /// `BoxBiConsumerOnce<T, U>`.
414    ///
415    /// # Type Parameters
416    ///
417    /// * `C` - The type of the next consumer
418    ///
419    /// # Parameters
420    ///
421    /// * `next` - The consumer to execute after the current operation. **Note:
422    ///   This parameter is passed by value and will transfer ownership.** Since
423    ///   `BoxBiConsumerOnce` cannot be cloned, the parameter will be consumed.
424    ///   Can be:
425    ///   - A closure: `|x: &T, y: &U|`
426    ///   - A `BoxBiConsumerOnce<T, U>`
427    ///   - Any type implementing `BiConsumerOnce<T, U>`
428    ///
429    /// # Returns
430    ///
431    /// Returns the composed `BoxBiConsumerOnce<T, U>`
432    ///
433    /// # Examples
434    ///
435    /// ```rust
436    /// use prism3_function::{BiConsumerOnce, FnBiConsumerOnceOps};
437    /// use std::sync::{Arc, Mutex};
438    ///
439    /// let log = Arc::new(Mutex::new(Vec::new()));
440    /// let l1 = log.clone();
441    /// let l2 = log.clone();
442    /// let chained = (move |x: &i32, y: &i32| {
443    ///     l1.lock().unwrap().push(*x + *y);
444    /// }).and_then(move |x: &i32, y: &i32| {
445    ///     l2.lock().unwrap().push(*x * *y);
446    /// }).and_then(|x: &i32, y: &i32| {
447    ///     println!("Result: {}, {}", x, y);
448    /// });
449    ///
450    /// chained.accept(&5, &3);
451    /// assert_eq!(*log.lock().unwrap(), vec![8, 15]);
452    /// ```
453    fn and_then<C>(self, next: C) -> BoxBiConsumerOnce<T, U>
454    where
455        Self: 'static,
456        C: BiConsumerOnce<T, U> + 'static,
457        T: 'static,
458        U: 'static,
459    {
460        let first = self;
461        let second = next;
462        BoxBiConsumerOnce::new(move |t, u| {
463            first(t, u);
464            second.accept(t, u);
465        })
466    }
467}
468
469/// Implements FnBiConsumerOnceOps for all closure types
470impl<T, U, F> FnBiConsumerOnceOps<T, U> for F where F: FnOnce(&T, &U) {}
471
472// =======================================================================
473// 5. BoxConditionalBiConsumerOnce - Box-based Conditional BiConsumerOnce
474// =======================================================================
475
476/// BoxConditionalBiConsumerOnce struct
477///
478/// A conditional one-time bi-consumer that only executes when a predicate is satisfied.
479/// Uses `BoxBiConsumerOnce` and `BoxBiPredicate` for single ownership semantics.
480///
481/// This type is typically created by calling `BoxBiConsumerOnce::when()` and is
482/// designed to work with the `or_else()` method to create if-then-else logic.
483///
484/// # Features
485///
486/// - **Single Ownership**: Not cloneable, consumes `self` on use
487/// - **Conditional Execution**: Only consumes when predicate returns `true`
488/// - **Chainable**: Can add `or_else` branch to create if-then-else logic
489/// - **Implements BiConsumerOnce**: Can be used anywhere a `BiConsumerOnce` is expected
490///
491/// # Examples
492///
493/// ## Basic Conditional Execution
494///
495/// ```rust
496/// use prism3_function::{BiConsumerOnce, BoxBiConsumerOnce};
497/// use std::sync::{Arc, Mutex};
498///
499/// let log = Arc::new(Mutex::new(Vec::new()));
500/// let l = log.clone();
501/// let consumer = BoxBiConsumerOnce::new(move |x: &i32, y: &i32| {
502///     l.lock().unwrap().push(*x + *y);
503/// });
504/// let conditional = consumer.when(|x: &i32, y: &i32| *x > 0 && *y > 0);
505///
506/// conditional.accept(&5, &3);
507/// assert_eq!(*log.lock().unwrap(), vec![8]); // Executed
508/// ```
509///
510/// ## With or_else Branch
511///
512/// ```rust
513/// use prism3_function::{BiConsumerOnce, BoxBiConsumerOnce};
514/// use std::sync::{Arc, Mutex};
515///
516/// let log = Arc::new(Mutex::new(Vec::new()));
517/// let l1 = log.clone();
518/// let l2 = log.clone();
519/// let consumer = BoxBiConsumerOnce::new(move |x: &i32, y: &i32| {
520///     l1.lock().unwrap().push(*x + *y);
521/// }).when(|x: &i32, y: &i32| *x > 0 && *y > 0)
522///   .or_else(move |x: &i32, y: &i32| {
523///     l2.lock().unwrap().push(*x * *y);
524/// });
525///
526/// consumer.accept(&5, &3);
527/// assert_eq!(*log.lock().unwrap(), vec![8]); // when branch executed
528/// ```
529///
530/// # Author
531///
532/// Haixing Hu
533pub struct BoxConditionalBiConsumerOnce<T, U> {
534    consumer: BoxBiConsumerOnce<T, U>,
535    predicate: BoxBiPredicate<T, U>,
536}
537
538// Generate and_then and or_else methods using macro
539impl_box_conditional_consumer!(
540    BoxConditionalBiConsumerOnce<T, U>,
541    BoxBiConsumerOnce,
542    BiConsumerOnce
543);
544
545impl<T, U> BiConsumerOnce<T, U> for BoxConditionalBiConsumerOnce<T, U>
546where
547    T: 'static,
548    U: 'static,
549{
550    fn accept(self, first: &T, second: &U) {
551        if self.predicate.test(first, second) {
552            self.consumer.accept(first, second);
553        }
554    }
555
556    fn into_box(self) -> BoxBiConsumerOnce<T, U> {
557        let pred = self.predicate;
558        let consumer = self.consumer;
559        BoxBiConsumerOnce::new(move |t, u| {
560            if pred.test(t, u) {
561                consumer.accept(t, u);
562            }
563        })
564    }
565
566    fn into_fn(self) -> impl FnOnce(&T, &U) {
567        let pred = self.predicate;
568        let consumer = self.consumer;
569        move |t: &T, u: &U| {
570            if pred.test(t, u) {
571                consumer.accept(t, u);
572            }
573        }
574    }
575}
576
577// Use macro to generate Debug and Display implementations
578impl_conditional_consumer_debug_display!(BoxConditionalBiConsumerOnce<T, U>);