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}