Compare commits
4 Commits
868678fa00
...
main
Author | SHA1 | Date | |
---|---|---|---|
32595d41bb | |||
d16f8e333e | |||
4b16a23712 | |||
d395741822 |
348
Cargo.lock
generated
Normal file
348
Cargo.lock
generated
Normal file
@ -0,0 +1,348 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "example"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "example"
|
||||
version = "0.0.0"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
description = "Just a small sample of Rust code."
|
||||
repository = "/myrddin/example"
|
||||
@ -11,3 +11,4 @@ license = "Apache-2.0"
|
||||
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.45.1", features = ["full", "test-util"] }
|
||||
|
137
README.md
137
README.md
@ -1,6 +1,137 @@
|
||||
# example
|
||||
# Example
|
||||
|
||||
Just a small sample of Rust code.
|
||||
This repository demonstrates modern, idiomatic Rust through two parts:
|
||||
|
||||
1. **Foundational Rust Examples** – covering core concepts like lifetimes,
|
||||
error handling, async, and testing.
|
||||
2. **Advanced Async Job Runner** – a trait-based, concurrent task execution
|
||||
framework built using `tokio`.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Foundational Rust Examples
|
||||
|
||||
A lightweight collection of examples demonstrating essential Rust features,
|
||||
without relying on external dependencies (except `tokio`).
|
||||
|
||||
### Concepts Covered
|
||||
|
||||
- Arrays and Iteration
|
||||
- Lifetimes and Borrowing
|
||||
- Error Handling with `Result`, `?`, and custom messages
|
||||
- Async Programming using `#[tokio::main]`
|
||||
- File I/O with the standard library
|
||||
- Unit and async testing
|
||||
|
||||
### Key Functions
|
||||
|
||||
| Function | Description |
|
||||
|------------------|--------------------------------------------------------|
|
||||
| `longest` | Returns the longest string in a slice without copying. |
|
||||
| `read_and_parse` | Reads a file and parses its content into an integer. |
|
||||
| `run_tasks` | Demonstrates spawning and awaiting async tasks. |
|
||||
|
||||
### Testing
|
||||
|
||||
Includes both synchronous and asynchronous unit tests under a
|
||||
`#[cfg(test)]` module.
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Advanced Async Job Runner
|
||||
|
||||
An extensible, asynchronous job system using:
|
||||
|
||||
- Trait objects
|
||||
- Pinned futures (`Pin<Box<dyn Future>>`)
|
||||
- Tokio’s `JoinSet` for concurrent execution
|
||||
- Fully dynamic job management without macros
|
||||
|
||||
### Why It Matters
|
||||
|
||||
This system demonstrates:
|
||||
- Manual implementation of async trait behavior (without `async_trait`)
|
||||
- Advanced type management (`Box<dyn Trait>`, lifetimes, `Pin`)
|
||||
- Idiomatic use of `tokio::task::JoinSet` for parallelism
|
||||
|
||||
### Architecture
|
||||
|
||||
#### `Job` Trait
|
||||
|
||||
```rust
|
||||
pub trait Job: Send + Sync {
|
||||
fn name(&self) -> &str;
|
||||
fn run<'a>(&self) -> Pin<Box<dyn Future<Output = JobResult> + Send + 'a>>
|
||||
where Self: Sync + 'a;
|
||||
}
|
||||
```
|
||||
|
||||
#### `JobRunner` Struct
|
||||
|
||||
```rust
|
||||
pub struct JobRunner {
|
||||
jobs: Vec<Box<dyn Job>>
|
||||
}
|
||||
```
|
||||
|
||||
Manages and runs jobs concurrently.
|
||||
|
||||
#### Built-In Jobs
|
||||
|
||||
| Job Type | Behavior |
|
||||
|-----------|---------------------------|
|
||||
| `FileJob` | (Planned) Reads from disk |
|
||||
| `SleepJob`| Simulates a delay |
|
||||
| `MathJob` | Performs a math task |
|
||||
|
||||
### Usage
|
||||
|
||||
```rust
|
||||
let mut runner = JobRunner::new();
|
||||
runner.add_job(FileJob::new());
|
||||
runner.add_job(SleepJob::new());
|
||||
runner.add_job(MathJob::new());
|
||||
|
||||
for (name, result) in runner.run_all().await {
|
||||
match result {
|
||||
Ok(msg) => println!("{name}: {msg}"),
|
||||
Err(err) => eprintln!("{name}: {err}"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing & Extending
|
||||
|
||||
You can add unit or integration tests using mock jobs to simulate both success and failure.
|
||||
|
||||
Ideas for extension:
|
||||
- Make the Jobs actually do something
|
||||
- Interjob communication using channels
|
||||
- Create a QueueRunner that will run jobs in sequence
|
||||
- Create a ParallelRunner that will run jobs concurrently (simple rename)
|
||||
- Create a system that allows QueueRunners and ParallelRunners to be combined
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- Rust 1.76+ (for full language support)
|
||||
- [Tokio 1.37+](https://docs.rs/tokio) (for async runtime)
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```text
|
||||
src/
|
||||
├── adv_async.rs # Advanced async JobRunner
|
||||
├── basic.rs # Foundational examples
|
||||
├── info.rs # Crate information from Cargo
|
||||
├── lib.rs # Entry point for the library
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -8,7 +139,7 @@ Just a small sample of Rust code.
|
||||
|
||||
## Copyright & License
|
||||
|
||||
Copyright 2025 CyberMages LLC
|
||||
Copyright 2025 Jason Travis Smith
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this library except in compliance with the License.
|
||||
|
332
src/adv_async.rs
Normal file
332
src/adv_async.rs
Normal file
@ -0,0 +1,332 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Sealed with Magistamp.
|
||||
|
||||
//! An example job execution framework.
|
||||
//!
|
||||
//! It includes:
|
||||
//! - A `Job` trait for defining asynchronous tasks.
|
||||
//! - A `JobRunner` struct for collecting and executing multiple jobs.
|
||||
//! - Concrete implementations of jobs (`FileJob`, `SleepJob`, `MathJob`).
|
||||
//!
|
||||
//! Jobs are run concurrently using Tokio, and results are gathered
|
||||
//! with proper error handling.
|
||||
|
||||
use std::io::Write;
|
||||
use std::pin::Pin;
|
||||
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
|
||||
|
||||
/// Run a predefined set of jobs asynchronously.
|
||||
///
|
||||
/// This function creates a `JobRunner`, adds three different jobs,
|
||||
/// and executes them concurrently. Results are printed to stdout,
|
||||
/// and any errors are reported to stderr.
|
||||
pub async fn run_jobs()
|
||||
{
|
||||
let mut runner: JobRunner = JobRunner::new();
|
||||
|
||||
runner.add_job(FileJob::new());
|
||||
runner.add_job(SleepJob::new());
|
||||
runner.add_job(MathJob::new());
|
||||
|
||||
for result in runner.run_all().await.iter()
|
||||
{
|
||||
match &result.1
|
||||
{
|
||||
Ok(msg) =>
|
||||
{
|
||||
println!("{}: {}", result.0, msg);
|
||||
}
|
||||
|
||||
Err(error) =>
|
||||
{
|
||||
eprintln!("{}: {}", result.0, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A Job will return this after running. The traits need to be Send
|
||||
/// because they maybe sent between threads.
|
||||
type JobResult = Result<String, Box<dyn std::error::Error + Send>>;
|
||||
|
||||
/// The Future task that Jobs will return that can later be run.
|
||||
/// Here we Pin a heap allocated future so that it can be referenced safely.
|
||||
/// The output is then set to the desired output of the function.
|
||||
/// This is the may way to do 'async dyn traits'.
|
||||
type PinnedFuture<'a> =
|
||||
Pin<Box<dyn core::future::Future<Output = JobResult> + Send + 'a>>;
|
||||
|
||||
|
||||
|
||||
/// An async trait that can be used as a dyn trait.
|
||||
pub trait Job: Send + Sync
|
||||
{
|
||||
/// Retrieve the name of the Job.
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Run the Job.
|
||||
///
|
||||
/// This function needs to be async.
|
||||
/// Here we Pin a heap allocated future so that it can be referenced safely.
|
||||
/// The output is then set to the desired output of the function.
|
||||
///
|
||||
/// Inside the implementation all you need to do is:
|
||||
/// ```ignore
|
||||
/// Box::pin(async move
|
||||
/// {
|
||||
/// // Place your functions code here.
|
||||
/// })
|
||||
/// ```
|
||||
fn run<'a>(&self) -> PinnedFuture<'a>
|
||||
where Self: Sync + 'a;
|
||||
}
|
||||
|
||||
|
||||
/// A struct to hold and execute multiple jobs.
|
||||
pub struct JobRunner
|
||||
{
|
||||
jobs: Vec<Box<dyn Job>>
|
||||
}
|
||||
|
||||
impl JobRunner
|
||||
{
|
||||
/// Create a new JobRunner with an empty job list.
|
||||
pub fn new() -> Self
|
||||
{
|
||||
JobRunner
|
||||
{
|
||||
jobs: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new job to the runner.
|
||||
pub fn add_job(&mut self, job: impl Job + 'static)
|
||||
{
|
||||
self.jobs.push(Box::new(job));
|
||||
}
|
||||
|
||||
/// Run all added jobs concurrently and collect their results.
|
||||
///
|
||||
/// Uses Tokio to spawn concurrent tasks for each job. After
|
||||
/// completion, all jobs are cleared from the runner.
|
||||
pub async fn run_all(&mut self) -> Vec<(String, JobResult)>
|
||||
{
|
||||
let tasks: tokio::task::JoinSet<(String, JobResult)> =
|
||||
self.jobs
|
||||
.iter()
|
||||
.map(|j| {
|
||||
let name = j.name().to_string();
|
||||
let fut = j.run();
|
||||
|
||||
async move { (name, fut.await) }
|
||||
})
|
||||
.collect();
|
||||
|
||||
let result = tasks.join_all().await;
|
||||
|
||||
self.jobs.clear();
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A dummy job simulating a file operation.
|
||||
pub struct FileJob {}
|
||||
|
||||
impl FileJob
|
||||
{
|
||||
/// Create a new FileJob instance.
|
||||
fn new() -> Self
|
||||
{
|
||||
FileJob
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the Job trait for FileJob.
|
||||
impl Job for FileJob
|
||||
{
|
||||
/// Retrieve the name of the Job.
|
||||
fn name(&self) -> &str
|
||||
{
|
||||
"File Job"
|
||||
}
|
||||
|
||||
/// Run the File Job.
|
||||
fn run<'a>(&self) -> PinnedFuture<'a>
|
||||
where Self: Sync + 'a
|
||||
{
|
||||
Box::pin(async move {
|
||||
let mut write_target = std::env::temp_dir();
|
||||
write_target.push("file_job");
|
||||
write_target.set_extension("txt");
|
||||
|
||||
let mut file = std::fs::File::create(write_target)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
||||
file.write_all(b"Jason is an awesome programmer!!")
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
||||
|
||||
Ok(String::from("File written"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A dummy job simulating a sleep or delay operation.
|
||||
pub struct SleepJob {}
|
||||
|
||||
impl SleepJob
|
||||
{
|
||||
/// Create a new SleepJob instance.
|
||||
fn new() -> Self
|
||||
{
|
||||
SleepJob
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the Job trait for SleepJob.
|
||||
impl Job for SleepJob
|
||||
{
|
||||
/// Retrieve the name of the Job.
|
||||
fn name(&self) -> &str
|
||||
{
|
||||
"Sleep Job"
|
||||
}
|
||||
|
||||
/// Run the Sleep Job.
|
||||
fn run<'a>(&self) -> PinnedFuture<'a>
|
||||
where Self: Sync + 'a
|
||||
{
|
||||
Box::pin(async move
|
||||
{
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
Ok(String::from("Zzzzzzzzzzz"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A dummy job simulating a math operation.
|
||||
pub struct MathJob {}
|
||||
|
||||
impl MathJob
|
||||
{
|
||||
/// Create a new MathJob instance.
|
||||
fn new() -> Self
|
||||
{
|
||||
MathJob
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the Job trait for MathJob.
|
||||
impl Job for MathJob
|
||||
{
|
||||
/// Retrieve the name of the Job.
|
||||
fn name(&self) -> &str
|
||||
{
|
||||
"Math Job"
|
||||
}
|
||||
|
||||
/// Run the Math Job.
|
||||
fn run<'a>(&self) -> PinnedFuture<'a>
|
||||
where Self: Sync + 'a
|
||||
{
|
||||
Box::pin(async move {
|
||||
let _ans = 10 * 4 + 2;
|
||||
Ok(String::from("Math stuff"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests
|
||||
{
|
||||
use super::*;
|
||||
use tokio::time::Instant;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_job()
|
||||
{
|
||||
let job = FileJob::new();
|
||||
assert_eq!(job.name(), "File Job");
|
||||
|
||||
let result = job.run().await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "File written");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sleep_job()
|
||||
{
|
||||
let job = SleepJob::new();
|
||||
assert_eq!(job.name(), "Sleep Job");
|
||||
|
||||
let start = Instant::now();
|
||||
let result = job.run().await;
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "Zzzzzzzzzzz");
|
||||
assert!(elapsed >= Duration::from_millis(500), "Sleep duration too short");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_math_job()
|
||||
{
|
||||
let job = MathJob::new();
|
||||
assert_eq!(job.name(), "Math Job");
|
||||
|
||||
let result = job.run().await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "Math stuff");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_job_runner_executes_all_jobs()
|
||||
{
|
||||
let mut runner = JobRunner::new();
|
||||
runner.add_job(FileJob::new());
|
||||
runner.add_job(SleepJob::new());
|
||||
runner.add_job(MathJob::new());
|
||||
|
||||
let mut results = runner.run_all().await;
|
||||
|
||||
// Ensure jobs are cleared after run
|
||||
assert!(runner.jobs.is_empty());
|
||||
|
||||
// Sort results by name for consistent testing
|
||||
results.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
assert_eq!(results.len(), 3);
|
||||
|
||||
assert_eq!(results[0].0, "File Job");
|
||||
assert_eq!(results[0].1.as_ref().unwrap(), "File written");
|
||||
|
||||
assert_eq!(results[1].0, "Math Job");
|
||||
assert_eq!(results[1].1.as_ref().unwrap(), "Math stuff");
|
||||
|
||||
assert_eq!(results[2].0, "Sleep Job");
|
||||
assert_eq!(results[2].1.as_ref().unwrap(), "Zzzzzzzzzzz");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_job_runner_with_no_jobs()
|
||||
{
|
||||
let mut runner = JobRunner::new();
|
||||
let results = runner.run_all().await;
|
||||
assert!(results.is_empty());
|
||||
}
|
||||
}
|
207
src/basic.rs
Normal file
207
src/basic.rs
Normal file
@ -0,0 +1,207 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Sealed with Magistamp.
|
||||
|
||||
//! This demonstrates several foundational Rust concepts.
|
||||
//!
|
||||
//! It includes working examples of:
|
||||
//!
|
||||
//! - **Arrays and iteration**
|
||||
//! - **Error handling with `Result` and `?`**
|
||||
//! - **File I/O using the standard library**
|
||||
//! - **Asynchronous programming with `tokio`**
|
||||
//! - **Basic unit and async testing**
|
||||
//!
|
||||
//! ## Included Functions
|
||||
//!
|
||||
//! - `longest`: Finds the longest string in a slice without copying.
|
||||
//! - `read_and_parse`: Reads a file and parses its contents as an integer.
|
||||
//! - `run_tasks`: Spawns multiple asynchronous tasks and waits for them to
|
||||
//! complete.
|
||||
//!
|
||||
//! Unit tests are included under the `#[cfg(test)]` module to validate
|
||||
//! functionality and show best practices.
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
|
||||
|
||||
/// Takes a list of strings and returns the longest one without
|
||||
/// copying the strings.
|
||||
///
|
||||
/// The lifetime here is because we are returning the borrow reference of
|
||||
/// the passed in String. The lifetime tells the borrow checker that the
|
||||
/// returned reference needs to live as long as the owning String does.
|
||||
///
|
||||
/// ```
|
||||
/// # use example::basic::longest;
|
||||
/// let strings: [String; 5] = [String::from("Jason"),
|
||||
/// String::from("is"),
|
||||
/// String::from("an"),
|
||||
/// String::from("awesome"),
|
||||
/// String::from("programmer")];
|
||||
///
|
||||
/// assert_eq!(Some(strings[4].as_str()), longest(&strings));
|
||||
/// ```
|
||||
pub fn longest<'a>(strings: &'a [String]) -> Option<&'a str>
|
||||
{
|
||||
if strings.is_empty()
|
||||
{
|
||||
eprintln!("No strings detected");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Imperative programming style.
|
||||
//
|
||||
// ```Rust
|
||||
// This is the current longest string. (index, length)
|
||||
// let mut longest: (usize, usize) = (0, 0);
|
||||
// for (index, string) in strings.iter().enumerate()
|
||||
// {
|
||||
// if string.len() > longest.1
|
||||
// {
|
||||
// longest = (index, string.len());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Some(&strings[longest.0])
|
||||
// ```
|
||||
//
|
||||
// This can also be done with functional style:
|
||||
// When coding prefer the functional, declarative style.
|
||||
// It is just as fast or faster than the imperative style.
|
||||
strings.iter().max_by_key(|s| s.len()).map(|s| s.as_str())
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Read and parse the contents of a file as an integer.
|
||||
///
|
||||
/// ```
|
||||
/// # fn try_main() -> Result<(), Box<dyn std::error::Error>>
|
||||
/// # {
|
||||
/// # use example::basic::read_and_parse;
|
||||
/// use std::io::{ErrorKind, Write};
|
||||
///
|
||||
/// let val: i32 = 12345;
|
||||
/// let mut write_target = std::env::temp_dir();
|
||||
/// write_target.push("read_test");
|
||||
/// write_target.set_extension("txt");
|
||||
///
|
||||
/// let read_target = write_target.clone();
|
||||
///
|
||||
/// let mut file = std::fs::File::create(write_target)?;
|
||||
/// file.write_all(format!("{}", val).as_bytes())?;
|
||||
///
|
||||
///
|
||||
/// match read_target.to_str()
|
||||
/// {
|
||||
/// Some(path) =>
|
||||
/// {
|
||||
/// assert_eq!(val, read_and_parse(path)?);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// None =>
|
||||
/// {
|
||||
/// Err(Box::new(std::io::Error::new(ErrorKind::InvalidData,
|
||||
/// "Unable to turn target file into \
|
||||
/// a UTF-8 string.")))
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// # fn main()
|
||||
/// # {
|
||||
/// # try_main().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn read_and_parse<P>(path: P) -> Result<i32, Box<dyn std::error::Error>>
|
||||
where P: AsRef<std::path::Path>
|
||||
{
|
||||
let mut file = std::fs::File::open(path)?;
|
||||
let mut contents = String::new();
|
||||
|
||||
file.read_to_string(&mut contents)?;
|
||||
|
||||
let val: i32 = contents.parse()?;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Spawns 5 tasks, each sleeping for a different duration,
|
||||
/// then prints when each finishes.
|
||||
pub async fn run_tasks()
|
||||
{
|
||||
let tasks: tokio::task::JoinSet<_> =
|
||||
(0..5).map(|i| {
|
||||
async move {
|
||||
sleep(Duration::from_millis(500 + (i * 10))).await;
|
||||
println!("Finished!");
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let _result = tasks.join_all().await;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test
|
||||
{
|
||||
use std::io::Write;
|
||||
|
||||
use crate::basic::{longest, read_and_parse, run_tasks};
|
||||
|
||||
|
||||
#[test]
|
||||
fn longest_test() -> Result<(), String>
|
||||
{
|
||||
let strings: [String; 5] = [String::from("Jason"),
|
||||
String::from("is"),
|
||||
String::from("an"),
|
||||
String::from("awesome"),
|
||||
String::from("programmer")];
|
||||
|
||||
// Could also be Some(&*strings[4]). as_str() is easier to read though.
|
||||
assert_eq!(Some(strings[4].as_str()), longest(&strings));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_parse_test() -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let val: i32 = 12345;
|
||||
let mut write_target = std::env::temp_dir();
|
||||
write_target.push("read_test");
|
||||
write_target.set_extension("txt");
|
||||
|
||||
let read_target = write_target.clone();
|
||||
|
||||
let mut file = std::fs::File::create(write_target)?;
|
||||
file.write_all(format!("{}", val).as_bytes())?;
|
||||
|
||||
match read_target.to_str()
|
||||
{
|
||||
Some(path) =>
|
||||
{
|
||||
assert_eq!(val, read_and_parse(path)?);
|
||||
Ok(())
|
||||
}
|
||||
None =>
|
||||
{
|
||||
Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidData,
|
||||
"Unable to turn target file \
|
||||
into a UTF-8 string.")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn run_tasks_test()
|
||||
{
|
||||
run_tasks().await;
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Sealed with Magistamp.
|
||||
|
||||
//! This is where the cargo build information can be retrieved from.
|
||||
|
||||
|
||||
@ -23,8 +26,8 @@ pub fn get_name() -> &'static str
|
||||
}
|
||||
|
||||
|
||||
/// Returns the name of the program as defined by the CARGO_PKG_VERSION. This is
|
||||
/// set at compile time and comes from the Cargo.toml file.
|
||||
/// Returns the version of the program as defined by the CARGO_PKG_VERSION.
|
||||
/// This is set at compile time and comes from the Cargo.toml file.
|
||||
///
|
||||
/// If a value is not found, then it will return the not defined value.
|
||||
pub fn get_version() -> &'static str
|
||||
|
16
src/lib.rs
Normal file
16
src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Sealed with Magistamp.
|
||||
|
||||
//! This repository demonstrates modern, idiomatic Rust through two parts:
|
||||
//!
|
||||
//! 1. **Foundational Rust Examples** – covering core concepts like lifetimes,
|
||||
//! error handling, async, and testing.
|
||||
//!
|
||||
//! 2. **Advanced Async Job Runner** – a trait-based, concurrent task
|
||||
//! execution framework built using
|
||||
//! `tokio`.
|
||||
|
||||
|
||||
|
||||
pub mod basic;
|
||||
pub mod adv_async;
|
18
src/main.rs
18
src/main.rs
@ -1,18 +0,0 @@
|
||||
//! Just a small sample of Rust code.
|
||||
|
||||
mod info;
|
||||
|
||||
|
||||
|
||||
/// Print the version.
|
||||
fn print_version()
|
||||
{
|
||||
println!("{} v{}", info::get_name(), info::get_version());
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn main()
|
||||
{
|
||||
print_version();
|
||||
}
|
Reference in New Issue
Block a user