Skip to main content

struct_threads/
traits.rs

1/// A trait for defining a task that can be executed, typically in a separate thread.
2///
3/// Types implementing `Runnable` must be `Send` and `'static` to ensure they
4/// can be safely transferred across thread boundaries. By encapsulating state
5/// within a struct that implements this trait, you can easily manage complex
6/// thread initialization.
7///
8/// # Examples
9///
10/// ```rust
11/// use struct_threads::Runnable;
12///
13/// struct GreetingTask {
14///     name: String,
15/// }
16///
17/// impl Runnable for GreetingTask {
18///     type Output = String;
19///
20///     fn run(self) -> Self::Output {
21///         format!("Hello, {}!", self.name)
22///     }
23/// }
24/// ```
25pub trait Runnable: Send + 'static {
26    /// The type of value returned when the task completes.
27    type Output: Send + 'static;
28
29    /// Executes the task logic.
30    ///
31    /// This method consumes the task (`self`), meaning the state cannot be
32    /// reused after the thread has finished executing.
33    fn run(self) -> Self::Output;
34}
35
36/// An extension trait that provides a method to spawn a thread for a [`Runnable`] task.
37///
38/// This trait is automatically implemented for any type that implements `Runnable`.
39/// You do not need to implement this trait manually.
40pub trait Thread: Runnable {
41    /// Spawns a new standard library thread to execute the `run` method.
42    ///
43    /// This acts as a zero-cost abstraction over [`std::thread::spawn`].
44    ///
45    /// # Returns
46    ///
47    /// Returns a [`std::thread::JoinHandle`] that can be used to wait for the thread
48    /// to finish and extract its `Output`.
49    ///
50    /// # Examples
51    ///
52    /// ```rust
53    /// use struct_threads::{Runnable, Thread};
54    ///
55    /// struct MathTask(i32, i32);
56    ///
57    /// impl Runnable for MathTask {
58    ///     type Output = i32;
59    ///     fn run(self) -> Self::Output {
60    ///         self.0 + self.1
61    ///     }
62    /// }
63    ///
64    /// let task = MathTask(5, 7);
65    /// let handle = task.start(); // Provided by the Thread trait
66    ///
67    /// assert_eq!(handle.join().unwrap(), 12);
68    /// ```
69    fn start(self) -> std::thread::JoinHandle<Self::Output>;
70
71    /// Spawns a new thread using a custom [`std::thread::Builder`] to execute the `run` method.
72    ///
73    /// This allows you to configure thread properties such as name, stack size, or other
74    /// platform-specific options before spawning.
75    ///
76    /// # Arguments
77    ///
78    /// * `builder` - A [`std::thread::Builder`] configured with the desired thread properties
79    ///
80    /// # Returns
81    ///
82    /// Returns a [`std::thread::JoinHandle`] that can be used to wait for the thread
83    /// to finish and extract its `Output`.
84    ///
85    /// # Examples
86    ///
87    /// ```rust
88    /// use std::thread::Builder;
89    /// use struct_threads::{Runnable, Thread};
90    ///
91    /// struct MathTask(i32, i32);
92    ///
93    /// impl Runnable for MathTask {
94    ///     type Output = i32;
95    ///     fn run(self) -> Self::Output {
96    ///         self.0 + self.1
97    ///     }
98    /// }
99    ///
100    /// let task = MathTask(5, 7);
101    /// let builder = Builder::new()
102    ///     .name("math-thread".to_string())
103    ///     .stack_size(4 * 1024 * 1024); // 4 MB stack
104    ///
105    /// let handle = task.start_with_builder(builder);
106    /// assert_eq!(handle.join().unwrap(), 12);
107    /// ```
108    fn start_with_builder(
109        self,
110        builder: std::thread::Builder,
111    ) -> std::thread::JoinHandle<Self::Output>;
112}
113
114impl<T: Runnable> Thread for T {
115    fn start(self) -> std::thread::JoinHandle<Self::Output> {
116        std::thread::spawn(move || self.run())
117    }
118    fn start_with_builder(
119        self,
120        builder: std::thread::Builder,
121    ) -> std::thread::JoinHandle<Self::Output> {
122        builder.spawn(move || self.run()).unwrap()
123    }
124}
125
126/// An extension trait that provides a method to run multiple [`Runnable`]'s in parallel.
127///
128/// This trait is automatically implemented for any `Vec<T>` where `T` implements `Runnable`.
129/// You do not need to implement this trait manually.
130///
131/// The parallel execution is optimized to use the number of available CPU cores,
132/// dividing the tasks into chunks and processing them concurrently.
133pub trait ParallelRun {
134    type Output: Send + 'static;
135
136    /// Spawns multiple threads to execute the `run` method of each task in parallel.
137    ///
138    /// The number of threads spawned is determined by the number of available CPU cores,
139    /// with tasks divided evenly among them. Each thread processes a chunk of tasks
140    /// sequentially, while chunks are processed in parallel.
141    ///
142    /// # Returns
143    ///
144    /// Returns a [`std::thread::Result<Vec<Self::Output>>`] containing the results of each task,
145    /// in the same order as the input vector. Returns an error if any thread panics.
146    ///
147    /// # Examples
148    ///
149    /// ```rust
150    /// use struct_threads::{Runnable, ParallelRun};
151    ///
152    /// struct MathTask(i32, i32);
153    ///
154    /// impl Runnable for MathTask {
155    ///    type Output = i32;
156    ///
157    ///   fn run(self) -> Self::Output {
158    ///       self.0 + self.1
159    ///   }
160    /// }
161    ///
162    /// let tasks = vec![MathTask(1, 2), MathTask(3, 4), MathTask(5, 6)];
163    ///
164    /// let results = tasks
165    ///     .par_run()
166    ///     .unwrap(); // Provided by the ParallelRun trait
167    /// assert_eq!(results, vec![3, 7, 11]);
168    /// ```
169    fn par_run(self) -> std::thread::Result<Vec<Self::Output>>;
170}
171
172impl<T: Runnable> ParallelRun for Vec<T> {
173    type Output = T::Output;
174
175    fn par_run(self) -> std::thread::Result<Vec<Self::Output>> {
176        let threads = std::thread::available_parallelism()
177            .map(|n| n.get())
178            .unwrap_or(1)
179            .min(self.len());
180
181        if threads == 0 {
182            return Ok(Vec::new());
183        }
184
185        let chunk_size = self.len().div_ceil(threads);
186
187        let mut iter = self.into_iter();
188        let mut handles = Vec::with_capacity(threads);
189
190        for _ in 0..threads {
191            let chunk = iter.by_ref().take(chunk_size).collect::<Vec<_>>();
192            let handle =
193                std::thread::spawn(move || chunk.into_iter().map(|t| t.run()).collect::<Vec<_>>());
194            handles.push(handle);
195        }
196
197        let results = handles
198            .into_iter()
199            .map(|h| h.join())
200            .collect::<Result<Vec<_>, _>>()?;
201        Ok(results.into_iter().flatten().collect())
202    }
203}
204
205/// A trait for defining an async task that can be executed, typically in a Tokio runtime.
206///
207/// Types implementing `AsyncRunnable` must be `Send` and `'static` to ensure they
208/// can be safely transferred across async task boundaries. This trait is designed for
209/// async operations and works seamlessly with the Tokio runtime when the `tokio` feature is enabled.
210///
211/// # Examples
212///
213/// ```rust
214/// use struct_threads::AsyncRunnable;
215///
216/// struct AsyncGreetingTask {
217///     name: String,
218/// }
219///
220/// impl AsyncRunnable for AsyncGreetingTask {
221///     type Output = String;
222///
223///     fn run(self) -> impl std::future::Future<Output = Self::Output> + Send {
224///         async move {
225///             // Simulate async work
226///             tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
227///             format!("Hello, {}!", self.name)
228///         }
229///     }
230/// }
231/// ```
232pub trait AsyncRunnable: Send + 'static {
233    /// The type of value returned when the async task completes.
234    type Output: Send + 'static;
235
236    /// Executes the async task logic.
237    ///
238    /// This method consumes the task (`self`) and returns a future that must be
239    /// awaited to get the result.
240    fn run(self) -> impl std::future::Future<Output = Self::Output> + Send;
241}
242
243/// An extension trait that provides a method to spawn a Tokio task for an [`AsyncRunnable`] task.
244///
245/// This trait is automatically implemented for any type that implements `AsyncRunnable`
246/// when the `tokio` feature is enabled. You do not need to implement this trait manually.
247///
248/// # Examples
249///
250/// ```rust
251/// use struct_threads::{AsyncRunnable, TokioTask};
252///
253/// struct AsyncMathTask(i32, i32);
254///
255/// impl AsyncRunnable for AsyncMathTask {
256///     type Output = i32;
257///
258///     fn run(self) -> impl std::future::Future<Output = Self::Output> + Send {
259///         async move {
260///             tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
261///             self.0 + self.1
262///         }
263///     }
264/// }
265///
266/// #[tokio::main]
267/// async fn main() {
268///     let task = AsyncMathTask(5, 7);
269///     let handle = task.async_start(); // Provided by the TokioTask trait
270///
271///     assert_eq!(handle.await.unwrap(), 12);
272/// }
273/// ```
274#[cfg(feature = "tokio")]
275pub trait TokioTask: AsyncRunnable {
276    /// Spawns a new Tokio task to execute the `run` method.
277    ///
278    /// This acts as a zero-cost abstraction over [`tokio::task::spawn`].
279    ///
280    /// # Returns
281    ///
282    /// Returns a [`tokio::task::JoinHandle`] that can be awaited to get the task's output.
283    fn async_start(self) -> tokio::task::JoinHandle<Self::Output>;
284}
285
286#[cfg(feature = "tokio")]
287impl<T: AsyncRunnable> TokioTask for T {
288    fn async_start(self) -> tokio::task::JoinHandle<Self::Output> {
289        tokio::task::spawn(async move { self.run().await })
290    }
291}
292
293/// An extension trait that provides a method to run multiple [`AsyncRunnable`]'s in parallel using Tokio.
294///
295/// This trait is automatically implemented for any `Vec<T>` where `T` implements `AsyncRunnable`
296/// when the `tokio` feature is enabled. You do not need to implement this trait manually.
297///
298/// # Examples
299///
300/// ```rust
301/// use struct_threads::{AsyncRunnable, TokioParallelRun};
302///
303/// struct AsyncMathTask(i32, i32);
304///
305/// impl AsyncRunnable for AsyncMathTask {
306///     type Output = i32;
307///
308///     fn run(self) -> impl std::future::Future<Output = Self::Output> + Send {
309///         async move {
310///             tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
311///             self.0 + self.1
312///         }
313///     }
314/// }
315///
316/// #[tokio::main]
317/// async fn main() {
318///     let tasks = vec![
319///         AsyncMathTask(1, 2),
320///         AsyncMathTask(3, 4),
321///         AsyncMathTask(5, 6)
322///     ];
323///
324///     let results = tasks.async_par_run().await.unwrap();
325///     assert_eq!(results, vec![3, 7, 11]);
326/// }
327/// ```
328#[cfg(feature = "tokio")]
329pub trait TokioParallelRun {
330    /// The type of output produced by each task.
331    type Output: Send + 'static;
332
333    /// Spawns multiple Tokio tasks to execute the `run` method of each task in parallel.
334    ///
335    /// All tasks are spawned concurrently and their results are collected in the same order
336    /// as the input vector.
337    ///
338    /// # Returns
339    ///
340    /// Returns a future that resolves to `Result<Vec<Self::Output>, tokio::task::JoinError>`
341    /// containing the results of each task, or a join error if any task panics.
342    fn async_par_run(
343        self,
344    ) -> impl std::future::Future<Output = Result<Vec<Self::Output>, tokio::task::JoinError>> + Send;
345}
346
347#[cfg(feature = "tokio")]
348impl<T: AsyncRunnable> TokioParallelRun for Vec<T> {
349    type Output = T::Output;
350
351    fn async_par_run(
352        self,
353    ) -> impl std::future::Future<Output = Result<Vec<Self::Output>, tokio::task::JoinError>> + Send
354    {
355        async move {
356            let handles: Vec<_> = self
357                .into_iter()
358                .map(|t| tokio::task::spawn(async { t.run().await }))
359                .collect();
360            let mut result = Vec::with_capacity(handles.len());
361
362            for handle in handles {
363                result.push(handle.await?);
364            }
365            Ok(result)
366        }
367    }
368}