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>);