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 std::fmt;
25
26use crate::{
27 suppliers::supplier_once::SupplierOnce,
28 tasks::callable::BoxCallable,
29};
30
31// ============================================================================
32// Runnable Trait
33// ============================================================================
34
35/// A fallible one-time action.
36///
37/// `Runnable<E>` consumes itself and returns `Result<(), E>`. It is a semantic
38/// specialization of `SupplierOnce<Result<(), E>>` for executable actions and
39/// deferred side effects.
40///
41/// # Type Parameters
42///
43/// * `E` - The error value returned when the action fails.
44///
45/// # Examples
46///
47/// ```rust
48/// use qubit_function::Runnable;
49///
50/// let task = || Ok::<(), String>(());
51/// assert_eq!(task.run(), Ok(()));
52/// ```
53///
54/// # Author
55///
56/// Haixing Hu
57pub trait Runnable<E> {
58 /// Executes the action, consuming `self`.
59 ///
60 /// # Returns
61 ///
62 /// Returns `Ok(())` when the action succeeds, or `Err(E)` when it fails.
63 /// The exact error meaning is defined by the concrete runnable.
64 fn run(self) -> Result<(), E>;
65
66 /// Converts this runnable into a boxed runnable.
67 ///
68 /// # Returns
69 ///
70 /// A `BoxRunnable<E>` that executes this runnable when `run()` is invoked.
71 fn into_box(self) -> BoxRunnable<E>
72 where
73 Self: Sized + 'static,
74 {
75 BoxRunnable::new(move || self.run())
76 }
77
78 /// Converts this runnable into a closure.
79 ///
80 /// # Returns
81 ///
82 /// A closure implementing `FnOnce() -> Result<(), E>`.
83 fn into_fn(self) -> impl FnOnce() -> Result<(), E>
84 where
85 Self: Sized + 'static,
86 {
87 move || self.run()
88 }
89
90 /// Converts this runnable into a boxed runnable without consuming `self`.
91 ///
92 /// The method clones `self` and boxes the clone. Use this for cloneable
93 /// runnable values that need to be reused after boxing.
94 ///
95 /// # Returns
96 ///
97 /// A new `BoxRunnable<E>` built from a clone of this runnable.
98 fn to_box(&self) -> BoxRunnable<E>
99 where
100 Self: Clone + Sized + 'static,
101 {
102 self.clone().into_box()
103 }
104
105 /// Converts this runnable into a closure without consuming `self`.
106 ///
107 /// The method clones `self` and returns a one-time closure that executes
108 /// the clone.
109 ///
110 /// # Returns
111 ///
112 /// A closure implementing `FnOnce() -> Result<(), E>`.
113 fn to_fn(&self) -> impl FnOnce() -> Result<(), E>
114 where
115 Self: Clone + Sized + 'static,
116 {
117 self.clone().into_fn()
118 }
119
120 /// Converts this runnable into a callable returning unit.
121 ///
122 /// # Returns
123 ///
124 /// A `BoxCallable<(), E>` that executes this runnable and returns
125 /// `Ok(())` on success.
126 fn into_callable(self) -> BoxCallable<(), E>
127 where
128 Self: Sized + 'static,
129 {
130 BoxCallable::new(move || self.run())
131 }
132}
133
134// ============================================================================
135// BoxRunnable
136// ============================================================================
137
138/// Box-based one-time runnable.
139///
140/// `BoxRunnable<E>` stores a `Box<dyn FnOnce() -> Result<(), E>>` and can be
141/// executed only once. It is the boxed concrete implementation of
142/// [`Runnable`].
143///
144/// # Type Parameters
145///
146/// * `E` - The error value returned when the action fails.
147///
148/// # Examples
149///
150/// ```rust
151/// use qubit_function::{BoxRunnable, Runnable};
152///
153/// let task = BoxRunnable::new(|| Ok::<(), String>(()));
154/// assert_eq!(task.run(), Ok(()));
155/// ```
156///
157/// # Author
158///
159/// Haixing Hu
160pub struct BoxRunnable<E> {
161 /// The one-time closure executed by this runnable.
162 function: Box<dyn FnOnce() -> Result<(), E>>,
163 /// The optional name of this runnable.
164 name: Option<String>,
165}
166
167impl<E> BoxRunnable<E> {
168 /// Creates a new boxed runnable.
169 ///
170 /// # Parameters
171 ///
172 /// * `function` - The one-time closure executed by this runnable.
173 ///
174 /// # Returns
175 ///
176 /// A new unnamed `BoxRunnable<E>`.
177 #[inline]
178 pub fn new<F>(function: F) -> Self
179 where
180 F: FnOnce() -> Result<(), E> + 'static,
181 {
182 Self {
183 function: Box::new(function),
184 name: None,
185 }
186 }
187
188 /// Creates a new named boxed runnable.
189 ///
190 /// # Parameters
191 ///
192 /// * `name` - Name used by `Debug` and `Display`.
193 /// * `function` - The one-time closure executed by this runnable.
194 ///
195 /// # Returns
196 ///
197 /// A new named `BoxRunnable<E>`.
198 #[inline]
199 pub fn new_with_name<F>(name: &str, function: F) -> Self
200 where
201 F: FnOnce() -> Result<(), E> + 'static,
202 {
203 Self {
204 function: Box::new(function),
205 name: Some(name.to_string()),
206 }
207 }
208
209 /// Creates a new boxed runnable with an optional name.
210 ///
211 /// # Parameters
212 ///
213 /// * `function` - The one-time closure executed by this runnable.
214 /// * `name` - Optional name used by `Debug` and `Display`.
215 ///
216 /// # Returns
217 ///
218 /// A new `BoxRunnable<E>`.
219 #[inline]
220 pub fn new_with_optional_name<F>(function: F, name: Option<String>) -> Self
221 where
222 F: FnOnce() -> Result<(), E> + 'static,
223 {
224 Self {
225 function: Box::new(function),
226 name,
227 }
228 }
229
230 /// Creates a boxed runnable from a one-time supplier.
231 ///
232 /// This is an explicit bridge from `SupplierOnce<Result<(), E>>` to
233 /// `Runnable<E>`.
234 ///
235 /// # Parameters
236 ///
237 /// * `supplier` - The supplier that produces the runnable result.
238 ///
239 /// # Returns
240 ///
241 /// A new `BoxRunnable<E>`.
242 #[inline]
243 pub fn from_supplier<S>(supplier: S) -> Self
244 where
245 S: SupplierOnce<Result<(), E>> + 'static,
246 {
247 Self::new(move || supplier.get())
248 }
249
250 /// Gets the optional runnable name.
251 ///
252 /// # Returns
253 ///
254 /// Returns `Some(&str)` if a name was set, or `None` otherwise.
255 #[inline]
256 pub fn name(&self) -> Option<&str> {
257 self.name.as_deref()
258 }
259
260 /// Sets the runnable name.
261 ///
262 /// # Parameters
263 ///
264 /// * `name` - The new name.
265 #[inline]
266 pub fn set_name(&mut self, name: &str) {
267 if self.name.as_deref() != Some(name) {
268 self.name = Some(name.to_owned());
269 }
270 }
271
272 /// Clears the runnable name.
273 #[inline]
274 pub fn clear_name(&mut self) {
275 self.name = None;
276 }
277
278 /// Chains another runnable after this runnable succeeds.
279 ///
280 /// The second runnable is not executed if this runnable returns `Err`.
281 ///
282 /// # Parameters
283 ///
284 /// * `next` - The runnable to execute after this runnable succeeds.
285 ///
286 /// # Returns
287 ///
288 /// A new runnable executing both actions in sequence.
289 #[inline]
290 pub fn and_then<N>(self, next: N) -> BoxRunnable<E>
291 where
292 N: Runnable<E> + 'static,
293 E: 'static,
294 {
295 let name = self.name;
296 let function = self.function;
297 BoxRunnable::new_with_optional_name(
298 move || {
299 function()?;
300 next.run()
301 },
302 name,
303 )
304 }
305
306 /// Runs this runnable before a callable.
307 ///
308 /// The callable is not executed if this runnable returns `Err`.
309 ///
310 /// # Parameters
311 ///
312 /// * `callable` - The callable to execute after this runnable succeeds.
313 ///
314 /// # Returns
315 ///
316 /// A callable producing the second computation's result.
317 #[inline]
318 pub fn then_callable<R, C>(self, callable: C) -> BoxCallable<R, E>
319 where
320 C: crate::tasks::callable::Callable<R, E> + 'static,
321 R: 'static,
322 E: 'static,
323 {
324 let name = self.name;
325 let function = self.function;
326 BoxCallable::new_with_optional_name(
327 move || {
328 function()?;
329 callable.call()
330 },
331 name,
332 )
333 }
334}
335
336impl<E> Runnable<E> for BoxRunnable<E> {
337 /// Executes the boxed runnable.
338 #[inline]
339 fn run(self) -> Result<(), E> {
340 (self.function)()
341 }
342
343 /// Returns this boxed runnable without re-boxing it.
344 #[inline]
345 fn into_box(self) -> BoxRunnable<E> {
346 self
347 }
348
349 /// Extracts the underlying one-time closure.
350 #[inline]
351 fn into_fn(self) -> impl FnOnce() -> Result<(), E> {
352 self.function
353 }
354
355 /// Converts this boxed runnable into a boxed callable while preserving its
356 /// name.
357 #[inline]
358 fn into_callable(self) -> BoxCallable<(), E>
359 where
360 Self: Sized + 'static,
361 {
362 let name = self.name;
363 let function = self.function;
364 BoxCallable::new_with_optional_name(function, name)
365 }
366}
367
368impl<E> SupplierOnce<Result<(), E>> for BoxRunnable<E> {
369 /// Executes the boxed runnable as a one-time supplier of `Result<(), E>`.
370 #[inline]
371 fn get(self) -> Result<(), E> {
372 self.run()
373 }
374}
375
376impl<F, E> Runnable<E> for F
377where
378 F: FnOnce() -> Result<(), E>,
379{
380 /// Executes the closure as a runnable.
381 #[inline]
382 fn run(self) -> Result<(), E> {
383 self()
384 }
385
386 /// Converts the closure to a boxed runnable.
387 #[inline]
388 fn into_box(self) -> BoxRunnable<E>
389 where
390 Self: Sized + 'static,
391 {
392 BoxRunnable::new(self)
393 }
394
395 /// Returns the closure unchanged.
396 #[inline]
397 fn into_fn(self) -> impl FnOnce() -> Result<(), E>
398 where
399 Self: Sized + 'static,
400 {
401 self
402 }
403}
404
405impl<E> fmt::Debug for BoxRunnable<E> {
406 /// Formats this boxed runnable for debugging.
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 f.debug_struct("BoxRunnable")
409 .field("name", &self.name)
410 .field("function", &"<function>")
411 .finish()
412 }
413}
414
415impl<E> fmt::Display for BoxRunnable<E> {
416 /// Formats this boxed runnable for display.
417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 match &self.name {
419 Some(name) => write!(f, "BoxRunnable({name})"),
420 None => write!(f, "BoxRunnable"),
421 }
422 }
423}