From d395741822c737be4230ffc3ad72009d948189b3 Mon Sep 17 00:00:00 2001 From: Myrddin Dundragon Date: Fri, 27 Jun 2025 15:55:37 -0400 Subject: [PATCH] Committing a set of basic rust examples. This contains code and tests for several basic rust skills. --- Cargo.lock | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- README.md | 59 ++++++++- src/info.rs | 3 + src/main.rs | 257 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 658 insertions(+), 12 deletions(-) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..adb064e --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 6fe75bc..15d1dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/README.md b/README.md index e195b98..6951ab0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,67 @@ -# example +# Example -Just a small sample of Rust code. +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. + + +--- + + +## Testing & Extending + + +--- + + +## Requirements + +- Rust 1.76+ (for full language support) +- [Tokio 1.37+](https://docs.rs/tokio) (for async runtime) + + +--- + + +## Project Structure + +```text +src/ +├── info.rs # Crate information from Cargo. +├── main.rs # Foundational examples. +``` + --- ## 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. diff --git a/src/info.rs b/src/info.rs index e857280..0316847 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Sealed with Magistamp. + //! This is where the cargo build information can be retrieved from. diff --git a/src/main.rs b/src/main.rs index 2c4044f..1a04e26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,259 @@ -//! Just a small sample of Rust code. +// SPDX-License-Identifier: Apache-2.0 +// Sealed with Magistamp. -mod info; +//! # Skills Example +//! +//! This demonstrates several foundational Rust concepts in a simple, +//! self-contained example using only the tokio runtime and no other crates. +//! +//! 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. +//! +//! The `main` function is marked with the `#[tokio::main]` attribute and serves +//! as the entry point, demonstrating how to run async code in a typical binary. +//! +//! Unit tests are included under the `#[cfg(test)]` module to validate +//! functionality and show best practices. + +use std::io::Read; +use std::pin::Pin; + +use tokio::time::{sleep, Duration}; - -/// Print the version. -fn print_version() +/// An async trait that can be used as a dyn trait. +pub trait Job: Send + Sync { - println!("{} v{}", info::get_name(), info::get_version()); + fn name(&self) -> &str; + + /// 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) + -> Pin> + + Send + + 'a>> + where Self: Sync + 'a; +} + + +/// 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::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; + } + + // This is the current longest string. (index, length) + // + // This can also be done with functional style: + // strings.iter().max_by_key(|s| s.len()).map(|s| s.as_str()) + 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]) } -fn main() +/// Read and parse the contents of a file as an integer. +/// +/// ``` +/// # fn try_main() -> Result<(), Box> +/// # { +/// # use example::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

(path: P) -> Result> + where P: AsRef { - print_version(); + 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; +} + + + +/// Where everything gets started. +/// +/// That's not really true. Rust sets up a +/// function that calls this one, which is similar to what tokio does when +/// you use the tokio::main attribute. However, this is the common starting +/// point for Rust binaries. +/// +/// If you wanted to handle the actual main on your own do something +/// like below. Be warned that you will need to parse the arguments passed +/// to your program yourself and it is a different starting function call +/// on certain platforms. +/// ```ignore +/// #[no_mangle] +/// #[inline(never)] +/// #[start] +/// pub extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 +/// { +/// } +/// ``` +#[tokio::main] +async fn main() +{ + run_tasks().await; +} + + + +#[cfg(test)] +mod test +{ + use std::io::Write; + + use crate::{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> + { + 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; + } }