From 57242caa521e893369c998b8bb3ab148672a006a Mon Sep 17 00:00:00 2001 From: David Lishchyshen Date: Thu, 26 Feb 2026 11:09:37 +0200 Subject: [PATCH 1/6] chore: Add tokio deps --- Cargo.lock | 16 ++++++++++++++++ Cargo.toml | 8 +++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1617550..34a65c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,6 +275,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "plotters" version = "0.3.7" @@ -439,6 +445,7 @@ name = "struct-threads" version = "1.0.1" dependencies = [ "criterion", + "tokio", ] [[package]] @@ -462,6 +469,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/Cargo.toml b/Cargo.toml index 13da099..834c36e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,17 @@ readme = "README.md" repository = "https://github.com/AsfhtgkDavid/struct-threads" keywords = ["thread", "runnable", "struct", "task"] categories = ["concurrency", "rust-patterns"] + +[features] +default = [] +tokio = ["dep:tokio"] + [dependencies] +tokio = { version = "1.49.0", optional = true,features = ["rt"] } [dev-dependencies] criterion = "0.8.2" [[bench]] name = "par_run" -harness = false \ No newline at end of file +harness = false From 750370d0c9326e0778bb4eed533765b4e536cb68 Mon Sep 17 00:00:00 2001 From: David Lishchyshen Date: Thu, 26 Feb 2026 11:43:24 +0200 Subject: [PATCH 2/6] feat: Add async traits --- src/lib.rs | 5 ++++- src/traits.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3b4b42b..76daa6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,10 @@ pub mod traits; -pub use traits::{ParallelRun, Runnable, Thread}; +pub use traits::{ParallelRun, Runnable, Thread, AsyncRunnable}; + +#[cfg(feature = "tokio")] +pub use traits::TokioTask; #[cfg(test)] mod tests { diff --git a/src/traits.rs b/src/traits.rs index 9d161b8..628bea4 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -159,3 +159,21 @@ impl ParallelRun for Vec { Ok(results.into_iter().flatten().collect()) } } + +pub trait AsyncRunnable: Send + 'static { + type Output: Send + 'static; + + async fn run(self) -> Self::Output; +} + +#[cfg(feature = "tokio")] +pub trait TokioTask: AsyncRunnable { + fn async_start(self) -> tokio::task::JoinHandle; +} + +#[cfg(feature = "tokio")] +impl TokioTask for T { + fn async_start(self) -> tokio::task::JoinHandle { + tokio::task::spawn(async move { self.run().await }) + } +} \ No newline at end of file From 20ace4b68124a5cf1891aa6d82fd2d678aae1902 Mon Sep 17 00:00:00 2001 From: David Lishchyshen Date: Fri, 27 Feb 2026 12:50:53 +0200 Subject: [PATCH 3/6] feat: Add tokio parallel run --- src/lib.rs | 2 +- src/traits.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 76daa6e..94e3920 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ pub mod traits; pub use traits::{ParallelRun, Runnable, Thread, AsyncRunnable}; #[cfg(feature = "tokio")] -pub use traits::TokioTask; +pub use traits::{TokioTask, TokioParallelRun}; #[cfg(test)] mod tests { diff --git a/src/traits.rs b/src/traits.rs index 628bea4..9bc0876 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -176,4 +176,26 @@ impl TokioTask for T { fn async_start(self) -> tokio::task::JoinHandle { tokio::task::spawn(async move { self.run().await }) } +} + +#[cfg(feature = "tokio")] +pub trait TokioParallelRun { + type Output: Send + 'static; + + async fn async_par_run(self) -> Result, tokio::task::JoinError>; +} + +#[cfg(feature = "tokio")] +impl TokioParallelRun for Vec { + type Output = T::Output; + + async fn async_par_run(self) -> Result, tokio::task::JoinError> { + let handles = self.into_iter().map(|t| tokio::task::spawn(async { t.run().await })).collect(); + let mut result = Vec::with_capacity(handles.len()); + + for handle in handles { + result.push(handle.await?); + } + Ok(result) + } } \ No newline at end of file From 177ebb1c53d5d9bc5b7460e9c524a384ea7cb411 Mon Sep 17 00:00:00 2001 From: David Lishchyshen Date: Fri, 27 Feb 2026 13:14:11 +0200 Subject: [PATCH 4/6] test: Add async tests --- Cargo.lock | 12 +++++ Cargo.toml | 1 + src/lib.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/traits.rs | 20 ++++---- 4 files changed, 153 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34a65c1..402e9f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -476,6 +476,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 834c36e..903e7fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ tokio = { version = "1.49.0", optional = true,features = ["rt"] } [dev-dependencies] criterion = "0.8.2" +tokio = { version = "1.49.0", features = ["rt", "rt-multi-thread", "time", "macros"] } [[bench]] name = "par_run" diff --git a/src/lib.rs b/src/lib.rs index 94e3920..e93ef68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,4 +110,133 @@ mod tests { let handle = BuilderTask.start_with_builder(builder); assert_eq!(handle.join().unwrap(), "custom_thread".to_string()); } + + #[cfg(feature = "tokio")] + mod async_tests { + use super::*; + + struct AsyncTestTask(i32); + + impl AsyncRunnable for AsyncTestTask { + type Output = i32; + + fn run(self) -> impl std::future::Future + Send { + async move { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + self.0 * 2 + } + } + } + + #[tokio::test] + async fn test_async_task() { + let task = AsyncTestTask(21); + let handle = task.async_start(); + assert_eq!(handle.await.unwrap(), 42); + } + + #[tokio::test] + async fn test_async_parallel_run() { + let tasks = (0..10).map(AsyncTestTask).collect::>(); + let results = tasks.async_par_run().await.unwrap(); + assert_eq!(results, (0..10).map(|x| x * 2).collect::>()); + } + + #[tokio::test] + async fn test_async_parallel_run_empty() { + let tasks = Vec::::new(); + let results = tasks.async_par_run().await.unwrap(); + assert!(results.is_empty()); + } + + #[tokio::test] + async fn test_async_parallel_run_large() { + let tasks = (0..1_000).map(AsyncTestTask).collect::>(); + let results = tasks.async_par_run().await.unwrap(); + assert_eq!(results, (0..1_000).map(|x| x * 2).collect::>()); + } + + struct AsyncComplexTask { + value: i32, + multiplier: i32, + } + + impl AsyncRunnable for AsyncComplexTask { + type Output = i32; + + fn run(self) -> impl std::future::Future + Send { + async move { + tokio::time::sleep(tokio::time::Duration::from_millis(5)).await; + self.value * self.multiplier + } + } + } + + #[tokio::test] + async fn test_async_complex_task() { + let task = AsyncComplexTask { + value: 7, + multiplier: 6, + }; + let handle = task.async_start(); + assert_eq!(handle.await.unwrap(), 42); + } + + #[tokio::test] + async fn test_async_multiple_awaits() { + let task1 = AsyncTestTask(10); + let task2 = AsyncTestTask(20); + let task3 = AsyncTestTask(30); + + let handle1 = task1.async_start(); + let handle2 = task2.async_start(); + let handle3 = task3.async_start(); + + let result1 = handle1.await.unwrap(); + let result2 = handle2.await.unwrap(); + let result3 = handle3.await.unwrap(); + + assert_eq!(result1, 20); + assert_eq!(result2, 40); + assert_eq!(result3, 60); + } + + struct AsyncStringTask(String); + + impl AsyncRunnable for AsyncStringTask { + type Output = String; + + fn run(self) -> impl std::future::Future + Send { + async move { + tokio::time::sleep(tokio::time::Duration::from_millis(5)).await; + format!("Hello, {}!", self.0) + } + } + } + + #[tokio::test] + async fn test_async_string_output() { + let task = AsyncStringTask("World".to_string()); + let handle = task.async_start(); + assert_eq!(handle.await.unwrap(), "Hello, World!"); + } + + #[tokio::test] + async fn test_async_parallel_run_strings() { + let tasks = vec![ + AsyncStringTask("Alice".to_string()), + AsyncStringTask("Bob".to_string()), + AsyncStringTask("Charlie".to_string()), + ]; + let results = tasks.async_par_run().await.unwrap(); + assert_eq!( + results, + vec![ + "Hello, Alice!".to_string(), + "Hello, Bob!".to_string(), + "Hello, Charlie!".to_string() + ] + ); + } + } } diff --git a/src/traits.rs b/src/traits.rs index 9bc0876..2003fd8 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -163,7 +163,7 @@ impl ParallelRun for Vec { pub trait AsyncRunnable: Send + 'static { type Output: Send + 'static; - async fn run(self) -> Self::Output; + fn run(self) -> impl std::future::Future + Send; } #[cfg(feature = "tokio")] @@ -182,20 +182,22 @@ impl TokioTask for T { pub trait TokioParallelRun { type Output: Send + 'static; - async fn async_par_run(self) -> Result, tokio::task::JoinError>; + fn async_par_run(self) -> impl std::future::Future, tokio::task::JoinError>> + Send; } #[cfg(feature = "tokio")] impl TokioParallelRun for Vec { type Output = T::Output; - async fn async_par_run(self) -> Result, tokio::task::JoinError> { - let handles = self.into_iter().map(|t| tokio::task::spawn(async { t.run().await })).collect(); - let mut result = Vec::with_capacity(handles.len()); - - for handle in handles { - result.push(handle.await?); + fn async_par_run(self) -> impl std::future::Future, tokio::task::JoinError>> + Send { + async move { + let handles: Vec<_> = self.into_iter().map(|t| tokio::task::spawn(async { t.run().await })).collect(); + let mut result = Vec::with_capacity(handles.len()); + + for handle in handles { + result.push(handle.await?); + } + Ok(result) } - Ok(result) } } \ No newline at end of file From 2384ecd40a8028d0911df22533968bac4551e1fe Mon Sep 17 00:00:00 2001 From: David Lishchyshen Date: Fri, 27 Feb 2026 13:32:09 +0200 Subject: [PATCH 5/6] docs: Add docs for async and changelog --- CHANGELOG.md | 30 ++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 146 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 68 ++++++++++++++++++++- src/traits.rs | 161 +++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 402 insertions(+), 7 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7b4060c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.1.0] - 2026-02-27 + +### Added +- **Async Support**: New `tokio` feature flag for async task execution +- **AsyncRunnable trait**: Define async tasks that can be executed with Tokio runtime +- **TokioTask trait**: Extension trait providing `async_start()` method for spawning async tasks +- **TokioParallelRun trait**: Run multiple async tasks in parallel with `async_par_run()` method +- **Custom Thread Builder**: `start_with_builder()` method for customizing thread properties (name, stack size, etc.) +- **Parallel Execution**: `ParallelRun` trait for running multiple tasks in parallel across CPU cores +- Comprehensive documentation for all features +- Examples for async usage, parallel execution, and custom thread configuration + +### Changed +- Enhanced documentation with detailed examples for all traits +- Improved README with feature descriptions and usage examples + +## [1.0.0] - Initial Release + +### Added +- **Runnable trait**: Define tasks as structs implementing a simple trait +- **Thread trait**: Extension trait providing `start()` method for spawning threads +- Basic documentation and examples + diff --git a/Cargo.lock b/Cargo.lock index 402e9f4..98c16de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -442,7 +442,7 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "struct-threads" -version = "1.0.1" +version = "1.1.0" dependencies = [ "criterion", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 903e7fa..0371249 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "struct-threads" -version = "1.0.1" +version = "1.1.0" edition = "2024" authors = ["DavĂ­d Lishchyshen "] description = "A simple library providing a Thread extension trait for Runnable tasks." diff --git a/README.md b/README.md index ba39d49..fb7a5f7 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,23 @@ While `std::thread::spawn` is powerful, complex thread logic often requires pass - **Simplifies testing** by allowing you to instantiate thread state without immediately spawning it. - **Provides a clear contract** for the thread's return value via associated types. +## Features + +- **Default**: Provides `Runnable`, `Thread`, and `ParallelRun` traits for standard thread-based execution +- **`tokio`**: Adds `AsyncRunnable`, `TokioTask`, and `TokioParallelRun` traits for async task execution with Tokio runtime + ## Installation Install using `cargo`: ```bash cargo add struct-threads +``` +For async support with Tokio: + +```bash +cargo add struct-threads --features tokio ``` ## Basic Usage @@ -59,6 +69,68 @@ fn main() { ``` +## Advanced Usage: Parallel Execution + +You can run multiple tasks in parallel using the `ParallelRun` trait: + +```rust +use struct_threads::{Runnable, ParallelRun}; + +struct ComputeTask(i32); + +impl Runnable for ComputeTask { + type Output = i32; + + fn run(self) -> Self::Output { + // Perform some heavy computation + self.0 * self.0 + } +} + +fn main() { + let tasks = (0..100) + .map(ComputeTask) + .collect::>(); + + // Runs tasks in parallel across available CPU cores + let results = tasks.par_run().unwrap(); + + println!("Computed {} results", results.len()); +} +``` + +## Advanced Usage: Custom Thread Configuration + +Use `start_with_builder` to customize thread properties: + +```rust +use std::thread::Builder; +use struct_threads::{Runnable, Thread}; + +struct MyTask(i32); + +impl Runnable for MyTask { + type Output = i32; + + fn run(self) -> Self::Output { + self.0 * 2 + } +} + +fn main() { + let task = MyTask(21); + + let builder = Builder::new() + .name("my-custom-thread".to_string()) + .stack_size(4 * 1024 * 1024); // 4 MB stack + + let handle = task.start_with_builder(builder); + let result = handle.join().unwrap(); + + println!("Result: {}", result); +} +``` + ## Advanced Usage: Channels This pattern truly shines when your thread needs to communicate with the main thread or hold more complex state, like channels. @@ -92,6 +164,80 @@ fn main() { let result = rx.recv().unwrap(); println!("Received: {}", result); } +``` + +## Async Support (Tokio) + +Enable the `tokio` feature to use async tasks: + +```toml +[dependencies] +struct-threads = { version = "1.0", features = ["tokio"] } +tokio = { version = "1", features = ["rt", "macros"] } +``` + +### Basic Async Usage + +```rust +use struct_threads::{AsyncRunnable, TokioTask}; + +struct AsyncTask(i32); + +impl AsyncRunnable for AsyncTask { + type Output = i32; + + fn run(self) -> impl std::future::Future + Send { + async move { + // Perform async work + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + self.0 * 2 + } + } +} + +#[tokio::main] +async fn main() { + let task = AsyncTask(21); + + // The .async_start() method is provided by the TokioTask trait + let handle = task.async_start(); + + // Await the result + let result = handle.await.unwrap(); + + println!("Result: {}", result); +} +``` + +### Parallel Async Execution + +```rust +use struct_threads::{AsyncRunnable, TokioParallelRun}; + +struct AsyncComputeTask(i32); + +impl AsyncRunnable for AsyncComputeTask { + type Output = i32; + + fn run(self) -> impl std::future::Future + Send { + async move { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + self.0 * self.0 + } + } +} + +#[tokio::main] +async fn main() { + let tasks = (0..100) + .map(AsyncComputeTask) + .collect::>(); + + // Runs async tasks in parallel + let results = tasks.async_par_run().await.unwrap(); + + println!("Computed {} results", results.len()); +} ``` diff --git a/src/lib.rs b/src/lib.rs index e93ef68..65ed0e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,12 @@ //! `struct-threads` allows you to define your task state in a `struct`, implement the //! [`Runnable`] trait, and seamlessly spawn it using the [`Thread`] extension trait. //! -//! # Example +//! # Features +//! +//! - **Default**: Provides [`Runnable`], [`Thread`], and [`ParallelRun`] traits for standard thread-based execution +//! - **`tokio`**: Adds [`AsyncRunnable`], [`TokioTask`], and [`TokioParallelRun`] traits for async task execution with Tokio runtime +//! +//! # Basic Example //! //! ```rust //! use struct_threads::{Runnable, Thread}; @@ -22,9 +27,9 @@ //! assert_eq!(handle.join().unwrap(), 42); //! ``` //! -//! With `struct-threads`, you can also run multiple tasks in parallel using the [`ParallelRun`] extension trait. +//! # Parallel Execution //! -//! # Example +//! With `struct-threads`, you can also run multiple tasks in parallel using the [`ParallelRun`] extension trait. //! //! ```rust //! use struct_threads::{Runnable, ParallelRun}; @@ -47,6 +52,63 @@ //! //! assert_eq!(results, (0..10).map(|x| x * 2).collect::>()); //! ``` +//! +//! # Custom Thread Configuration +//! +//! You can customize thread properties using the [`Thread::start_with_builder`] method: +//! +//! ```rust +//! use std::thread::Builder; +//! use struct_threads::{Runnable, Thread}; +//! +//! struct MyTask(i32); +//! +//! impl Runnable for MyTask { +//! type Output = i32; +//! fn run(self) -> Self::Output { self.0 * 2 } +//! } +//! +//! let builder = Builder::new() +//! .name("custom-thread".to_string()) +//! .stack_size(4 * 1024 * 1024); +//! +//! let handle = MyTask(21).start_with_builder(builder); +//! assert_eq!(handle.join().unwrap(), 42); +//! ``` +//! +//! # Async Support (requires `tokio` feature) +//! +//! Enable async support by adding the `tokio` feature: +//! +//! ```toml +//! [dependencies] +//! struct-threads = { version = "1.0", features = ["tokio"] } +//! ``` +//! +//! Then use [`AsyncRunnable`] and [`TokioTask`]: +//! +//! ```rust,ignore +//! use struct_threads::{AsyncRunnable, TokioTask}; +//! +//! struct AsyncTask(i32); +//! +//! impl AsyncRunnable for AsyncTask { +//! type Output = i32; +//! +//! fn run(self) -> impl std::future::Future + Send { +//! async move { +//! tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; +//! self.0 * 2 +//! } +//! } +//! } +//! +//! #[tokio::main] +//! async fn main() { +//! let handle = AsyncTask(21).async_start(); +//! assert_eq!(handle.await.unwrap(), 42); +//! } +//! ``` pub mod traits; diff --git a/src/traits.rs b/src/traits.rs index 2003fd8..652b3c9 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -69,6 +69,42 @@ pub trait Thread: Runnable { fn start(self) -> std::thread::JoinHandle; /// Spawns a new thread using a custom [`std::thread::Builder`] to execute the `run` method. + /// + /// This allows you to configure thread properties such as name, stack size, or other + /// platform-specific options before spawning. + /// + /// # Arguments + /// + /// * `builder` - A [`std::thread::Builder`] configured with the desired thread properties + /// + /// # Returns + /// + /// Returns a [`std::thread::JoinHandle`] that can be used to wait for the thread + /// to finish and extract its `Output`. + /// + /// # Examples + /// + /// ```rust + /// use std::thread::Builder; + /// use struct_threads::{Runnable, Thread}; + /// + /// struct MathTask(i32, i32); + /// + /// impl Runnable for MathTask { + /// type Output = i32; + /// fn run(self) -> Self::Output { + /// self.0 + self.1 + /// } + /// } + /// + /// let task = MathTask(5, 7); + /// let builder = Builder::new() + /// .name("math-thread".to_string()) + /// .stack_size(4 * 1024 * 1024); // 4 MB stack + /// + /// let handle = task.start_with_builder(builder); + /// assert_eq!(handle.join().unwrap(), 12); + /// ``` fn start_with_builder( self, builder: std::thread::Builder, @@ -91,16 +127,22 @@ impl Thread for T { /// /// This trait is automatically implemented for any `Vec` where `T` implements `Runnable`. /// You do not need to implement this trait manually. +/// +/// The parallel execution is optimized to use the number of available CPU cores, +/// dividing the tasks into chunks and processing them concurrently. pub trait ParallelRun { type Output: Send + 'static; /// Spawns multiple threads to execute the `run` method of each task in parallel. /// - /// The number of threads spawned will be equal to the number of available CPU cores. + /// The number of threads spawned is determined by the number of available CPU cores, + /// with tasks divided evenly among them. Each thread processes a chunk of tasks + /// sequentially, while chunks are processed in parallel. /// /// # Returns /// - /// Returns a [`std::thread::Result>`] containing the results of each task, in the same order as the input vector. + /// Returns a [`std::thread::Result>`] containing the results of each task, + /// in the same order as the input vector. Returns an error if any thread panics. /// /// # Examples /// @@ -160,14 +202,84 @@ impl ParallelRun for Vec { } } +/// A trait for defining an async task that can be executed, typically in a Tokio runtime. +/// +/// Types implementing `AsyncRunnable` must be `Send` and `'static` to ensure they +/// can be safely transferred across async task boundaries. This trait is designed for +/// async operations and works seamlessly with the Tokio runtime when the `tokio` feature is enabled. +/// +/// # Examples +/// +/// ```rust +/// use struct_threads::AsyncRunnable; +/// +/// struct AsyncGreetingTask { +/// name: String, +/// } +/// +/// impl AsyncRunnable for AsyncGreetingTask { +/// type Output = String; +/// +/// fn run(self) -> impl std::future::Future + Send { +/// async move { +/// // Simulate async work +/// tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; +/// format!("Hello, {}!", self.name) +/// } +/// } +/// } +/// ``` pub trait AsyncRunnable: Send + 'static { + /// The type of value returned when the async task completes. type Output: Send + 'static; + /// Executes the async task logic. + /// + /// This method consumes the task (`self`) and returns a future that must be + /// awaited to get the result. fn run(self) -> impl std::future::Future + Send; } +/// An extension trait that provides a method to spawn a Tokio task for an [`AsyncRunnable`] task. +/// +/// This trait is automatically implemented for any type that implements `AsyncRunnable` +/// when the `tokio` feature is enabled. You do not need to implement this trait manually. +/// +/// # Examples +/// +/// ```rust +/// use struct_threads::{AsyncRunnable, TokioTask}; +/// +/// struct AsyncMathTask(i32, i32); +/// +/// impl AsyncRunnable for AsyncMathTask { +/// type Output = i32; +/// +/// fn run(self) -> impl std::future::Future + Send { +/// async move { +/// tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; +/// self.0 + self.1 +/// } +/// } +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let task = AsyncMathTask(5, 7); +/// let handle = task.async_start(); // Provided by the TokioTask trait +/// +/// assert_eq!(handle.await.unwrap(), 12); +/// } +/// ``` #[cfg(feature = "tokio")] pub trait TokioTask: AsyncRunnable { + /// Spawns a new Tokio task to execute the `run` method. + /// + /// This acts as a zero-cost abstraction over [`tokio::task::spawn`]. + /// + /// # Returns + /// + /// Returns a [`tokio::task::JoinHandle`] that can be awaited to get the task's output. fn async_start(self) -> tokio::task::JoinHandle; } @@ -178,10 +290,55 @@ impl TokioTask for T { } } +/// An extension trait that provides a method to run multiple [`AsyncRunnable`]'s in parallel using Tokio. +/// +/// This trait is automatically implemented for any `Vec` where `T` implements `AsyncRunnable` +/// when the `tokio` feature is enabled. You do not need to implement this trait manually. +/// +/// # Examples +/// +/// ```rust +/// use struct_threads::{AsyncRunnable, TokioParallelRun}; +/// +/// struct AsyncMathTask(i32, i32); +/// +/// impl AsyncRunnable for AsyncMathTask { +/// type Output = i32; +/// +/// fn run(self) -> impl std::future::Future + Send { +/// async move { +/// tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; +/// self.0 + self.1 +/// } +/// } +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let tasks = vec![ +/// AsyncMathTask(1, 2), +/// AsyncMathTask(3, 4), +/// AsyncMathTask(5, 6) +/// ]; +/// +/// let results = tasks.async_par_run().await.unwrap(); +/// assert_eq!(results, vec![3, 7, 11]); +/// } +/// ``` #[cfg(feature = "tokio")] pub trait TokioParallelRun { + /// The type of output produced by each task. type Output: Send + 'static; + /// Spawns multiple Tokio tasks to execute the `run` method of each task in parallel. + /// + /// All tasks are spawned concurrently and their results are collected in the same order + /// as the input vector. + /// + /// # Returns + /// + /// Returns a future that resolves to `Result, tokio::task::JoinError>` + /// containing the results of each task, or a join error if any task panics. fn async_par_run(self) -> impl std::future::Future, tokio::task::JoinError>> + Send; } From a9c444ab92200caff46514b2faeca2a9693e96e8 Mon Sep 17 00:00:00 2001 From: AsfhtgkDavid <79834378+AsfhtgkDavid@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:34:13 +0000 Subject: [PATCH 6/6] refactor: auto-format Rust code [skip ci] --- src/lib.rs | 4 ++-- src/traits.rs | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 65ed0e3..0ba403a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,10 +112,10 @@ pub mod traits; -pub use traits::{ParallelRun, Runnable, Thread, AsyncRunnable}; +pub use traits::{AsyncRunnable, ParallelRun, Runnable, Thread}; #[cfg(feature = "tokio")] -pub use traits::{TokioTask, TokioParallelRun}; +pub use traits::{TokioParallelRun, TokioTask}; #[cfg(test)] mod tests { diff --git a/src/traits.rs b/src/traits.rs index 652b3c9..e1cca17 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -339,16 +339,24 @@ pub trait TokioParallelRun { /// /// Returns a future that resolves to `Result, tokio::task::JoinError>` /// containing the results of each task, or a join error if any task panics. - fn async_par_run(self) -> impl std::future::Future, tokio::task::JoinError>> + Send; + fn async_par_run( + self, + ) -> impl std::future::Future, tokio::task::JoinError>> + Send; } #[cfg(feature = "tokio")] -impl TokioParallelRun for Vec { +impl TokioParallelRun for Vec { type Output = T::Output; - fn async_par_run(self) -> impl std::future::Future, tokio::task::JoinError>> + Send { + fn async_par_run( + self, + ) -> impl std::future::Future, tokio::task::JoinError>> + Send + { async move { - let handles: Vec<_> = self.into_iter().map(|t| tokio::task::spawn(async { t.run().await })).collect(); + let handles: Vec<_> = self + .into_iter() + .map(|t| tokio::task::spawn(async { t.run().await })) + .collect(); let mut result = Vec::with_capacity(handles.len()); for handle in handles { @@ -357,4 +365,4 @@ impl TokioParallelRun for Vec { Ok(result) } } -} \ No newline at end of file +}