Skip to main content

qubit_function/tasks/
runnable.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! # Runnable Types
10//!
11//! Provides fallible, one-time, zero-argument actions.
12//!
13//! A `Runnable<E>` is equivalent to `FnOnce() -> Result<(), E>`, but uses
14//! task-oriented vocabulary. Use it when the operation's side effect matters
15//! and only success or failure should be reported.
16//!
17//! The trait itself does not require `Send`; concurrent executors should add
18//! `+ Send + 'static` at their API boundary.
19//!
20//! # Author
21//!
22//! Haixing Hu
23
24use crate::{
25    macros::{
26        impl_box_once_conversions,
27        impl_closure_once_trait,
28        impl_common_name_methods,
29        impl_common_new_methods,
30    },
31    suppliers::macros::impl_supplier_debug_display,
32    suppliers::supplier_once::SupplierOnce,
33    tasks::callable::BoxCallable,
34};
35
36// ============================================================================
37// Runnable Trait
38// ============================================================================
39
40/// A fallible one-time action.
41///
42/// `Runnable<E>` consumes itself and returns `Result<(), E>`. It is a semantic
43/// specialization of `SupplierOnce<Result<(), E>>` for executable actions and
44/// deferred side effects.
45///
46/// # Type Parameters
47///
48/// * `E` - The error value returned when the action fails.
49///
50/// # Examples
51///
52/// ```rust
53/// use qubit_function::Runnable;
54///
55/// let task = || Ok::<(), String>(());
56/// assert_eq!(task.run(), Ok(()));
57/// ```
58///
59/// # Author
60///
61/// Haixing Hu
62pub trait Runnable<E> {
63    /// Executes the action, consuming `self`.
64    ///
65    /// # Returns
66    ///
67    /// Returns `Ok(())` when the action succeeds, or `Err(E)` when it fails.
68    /// The exact error meaning is defined by the concrete runnable.
69    fn run(self) -> Result<(), E>;
70
71    /// Converts this runnable into a boxed runnable.
72    ///
73    /// # Returns
74    ///
75    /// A `BoxRunnable<E>` that executes this runnable when `run()` is invoked.
76    fn into_box(self) -> BoxRunnable<E>
77    where
78        Self: Sized + 'static,
79    {
80        BoxRunnable::new(move || self.run())
81    }
82
83    /// Converts this runnable into a closure.
84    ///
85    /// # Returns
86    ///
87    /// A closure implementing `FnOnce() -> Result<(), E>`.
88    fn into_fn(self) -> impl FnOnce() -> Result<(), E>
89    where
90        Self: Sized + 'static,
91    {
92        move || self.run()
93    }
94
95    /// Converts this runnable into a boxed runnable without consuming `self`.
96    ///
97    /// The method clones `self` and boxes the clone. Use this for cloneable
98    /// runnable values that need to be reused after boxing.
99    ///
100    /// # Returns
101    ///
102    /// A new `BoxRunnable<E>` built from a clone of this runnable.
103    fn to_box(&self) -> BoxRunnable<E>
104    where
105        Self: Clone + Sized + 'static,
106    {
107        self.clone().into_box()
108    }
109
110    /// Converts this runnable into a closure without consuming `self`.
111    ///
112    /// The method clones `self` and returns a one-time closure that executes
113    /// the clone.
114    ///
115    /// # Returns
116    ///
117    /// A closure implementing `FnOnce() -> Result<(), E>`.
118    fn to_fn(&self) -> impl FnOnce() -> Result<(), E>
119    where
120        Self: Clone + Sized + 'static,
121    {
122        self.clone().into_fn()
123    }
124
125    /// Converts this runnable into a callable returning unit.
126    ///
127    /// # Returns
128    ///
129    /// A `BoxCallable<(), E>` that executes this runnable and returns
130    /// `Ok(())` on success.
131    fn into_callable(self) -> BoxCallable<(), E>
132    where
133        Self: Sized + 'static,
134    {
135        BoxCallable::new(move || self.run())
136    }
137}
138
139// ============================================================================
140// BoxRunnable
141// ============================================================================
142
143/// Box-based one-time runnable.
144///
145/// `BoxRunnable<E>` stores a `Box<dyn FnOnce() -> Result<(), E>>` and can be
146/// executed only once. It is the boxed concrete implementation of
147/// [`Runnable`].
148///
149/// # Type Parameters
150///
151/// * `E` - The error value returned when the action fails.
152///
153/// # Examples
154///
155/// ```rust
156/// use qubit_function::{BoxRunnable, Runnable};
157///
158/// let task = BoxRunnable::new(|| Ok::<(), String>(()));
159/// assert_eq!(task.run(), Ok(()));
160/// ```
161///
162/// # Author
163///
164/// Haixing Hu
165pub struct BoxRunnable<E> {
166    /// The one-time closure executed by this runnable.
167    function: Box<dyn FnOnce() -> Result<(), E>>,
168    /// The optional name of this runnable.
169    name: Option<String>,
170}
171
172impl<E> BoxRunnable<E> {
173    impl_common_new_methods!(
174        (FnOnce() -> Result<(), E> + 'static),
175        |function| Box::new(function),
176        "runnable"
177    );
178
179    /// Creates a boxed runnable from a one-time supplier.
180    ///
181    /// This is an explicit bridge from `SupplierOnce<Result<(), E>>` to
182    /// `Runnable<E>`.
183    ///
184    /// # Parameters
185    ///
186    /// * `supplier` - The supplier that produces the runnable result.
187    ///
188    /// # Returns
189    ///
190    /// A new `BoxRunnable<E>`.
191    #[inline]
192    pub fn from_supplier<S>(supplier: S) -> Self
193    where
194        S: SupplierOnce<Result<(), E>> + 'static,
195    {
196        Self::new(move || supplier.get())
197    }
198
199    impl_common_name_methods!("runnable");
200
201    /// Chains another runnable after this runnable succeeds.
202    ///
203    /// The second runnable is not executed if this runnable returns `Err`.
204    ///
205    /// # Parameters
206    ///
207    /// * `next` - The runnable to execute after this runnable succeeds.
208    ///
209    /// # Returns
210    ///
211    /// A new runnable executing both actions in sequence.
212    #[inline]
213    pub fn and_then<N>(self, next: N) -> BoxRunnable<E>
214    where
215        N: Runnable<E> + 'static,
216        E: 'static,
217    {
218        let name = self.name;
219        let function = self.function;
220        BoxRunnable::new_with_optional_name(
221            move || {
222                function()?;
223                next.run()
224            },
225            name,
226        )
227    }
228
229    /// Runs this runnable before a callable.
230    ///
231    /// The callable is not executed if this runnable returns `Err`.
232    ///
233    /// # Parameters
234    ///
235    /// * `callable` - The callable to execute after this runnable succeeds.
236    ///
237    /// # Returns
238    ///
239    /// A callable producing the second computation's result.
240    #[inline]
241    pub fn then_callable<R, C>(self, callable: C) -> BoxCallable<R, E>
242    where
243        C: crate::tasks::callable::Callable<R, E> + 'static,
244        R: 'static,
245        E: 'static,
246    {
247        let name = self.name;
248        let function = self.function;
249        BoxCallable::new_with_optional_name(
250            move || {
251                function()?;
252                callable.call()
253            },
254            name,
255        )
256    }
257}
258
259impl<E> Runnable<E> for BoxRunnable<E> {
260    /// Executes the boxed runnable.
261    #[inline]
262    fn run(self) -> Result<(), E> {
263        (self.function)()
264    }
265
266    impl_box_once_conversions!(BoxRunnable<E>, Runnable, FnOnce() -> Result<(), E>);
267
268    /// Converts this boxed runnable into a boxed callable while preserving its
269    /// name.
270    #[inline]
271    fn into_callable(self) -> BoxCallable<(), E>
272    where
273        Self: Sized + 'static,
274    {
275        let name = self.name;
276        let function = self.function;
277        BoxCallable::new_with_optional_name(function, name)
278    }
279}
280
281impl<E> SupplierOnce<Result<(), E>> for BoxRunnable<E> {
282    /// Executes the boxed runnable as a one-time supplier of `Result<(), E>`.
283    #[inline]
284    fn get(self) -> Result<(), E> {
285        self.run()
286    }
287}
288
289impl_closure_once_trait!(
290    Runnable<E>,
291    run,
292    BoxRunnable,
293    FnOnce() -> Result<(), E>
294);
295
296impl_supplier_debug_display!(BoxRunnable<E>);