Introduction
Welcome to the Rust Standard Library Showcase! 🦀
This comprehensive guide demonstrates 20 essential features from Rust’s standard library, designed to help developers understand and master Rust’s powerful capabilities.
🎯 What You’ll Learn
This showcase covers three levels of Rust standard library features:
Basic Features (1-10)
Core functionality every Rust developer should know:
- Threading and concurrency
- Time operations and timing
- Collections (HashMap, HashSet)
- File I/O operations
- Path manipulation
- Thread synchronization
- Environment variables
- Process execution
- Error handling with Result
- Option type usage
Intermediate Features (11-15)
Common patterns and advanced traits:
- Network operations (TCP)
- Iterator patterns
- Custom formatting
- Memory management
- Operator overloading
Advanced Features (16-20)
Advanced Rust concepts for complex scenarios:
- Pin and self-referential structs
- Future trait and async foundations
- Shared ownership patterns
- Dynamic typing with Any
- Panic handling and recovery
🚀 Getting Started
Each feature is documented on its own page with:
- Overview - What the feature does
- Code Example - Real code from the showcase
- Explanation - How it works
- Use Cases - When to use it
- Best Practices - Recommended patterns
💡 How to Use This Guide
- Sequential Learning: Start with Basic Features and progress to Advanced
- Reference: Jump to specific features as needed
- Hands-On: Run the actual code from the repository
- Practice: Experiment with the examples
📦 Repository
This documentation accompanies a working Rust project. You can:
# Clone the repository
git clone https://github.com/ioma8/rust-stdlib-showcase.git
cd rust-stdlib-showcase
# Build and run
cargo build
cargo run
The program executes all 20 features with real output you can see and experiment with.
🤝 Contributing
Found an improvement? Want to add more features? Check out the Contributing guide!
Ready to dive in? Start with Threading or jump to any feature in the sidebar! 🚀
Threading
Overview
Rust’s threading support provides safe, fearless concurrency through the std::thread module. Named threads, thread builders, and thread handles make it easy to create and manage concurrent execution.
Code Example
#![allow(unused)]
fn main() {
use std::thread;
let handler = thread::Builder::new()
.name("named thread".into())
.spawn(|| {
let handle = thread::current();
println!("Inner thread ID: {:?}, Name: {:?}",
handle.id(), handle.name());
})
.unwrap();
let main_handle = thread::current();
println!("Main thread ID: {:?}, Name: {:?}",
main_handle.id(), main_handle.name());
handler.join().unwrap();
}
Explanation
thread::Builder: Creates threads with custom configurations (name, stack size)spawn(): Launches a new thread with a closurethread::current(): Gets a handle to the current threadjoin(): Waits for a thread to complete and returns its result
Key Concepts
Thread Creation
Rust provides multiple ways to create threads:
- Simple:
thread::spawn(|| { /* code */ }) - With builder:
thread::Builder::new().name("name").spawn(|| { })
Thread Safety
Rust’s ownership system ensures thread safety at compile time. Data must be:
Send- can be transferred between threadsSync- can be shared between threads safely
Thread Handles
Thread handles (JoinHandle<T>) allow you to:
- Wait for thread completion with
join() - Get the thread’s return value
- Check if thread has finished
Use Cases
- Parallel Processing: Distribute work across CPU cores
- Background Tasks: Run long operations without blocking
- Concurrent Servers: Handle multiple client connections
- Data Pipeline: Process data in parallel stages
Best Practices
✅ Do:
- Use
thread::Builderfor named threads (easier debugging) - Always
join()threads or handle panics - Use channels or
Arc<Mutex<T>>for communication
❌ Don’t:
- Share mutable state without synchronization
- Create excessive threads (use thread pools)
- Ignore
join()results (may hide panics)
Related Features
- Synchronization - Arc and Mutex for shared state
- Time Operations - Thread sleep and timing
Learn More
Time Operations
Overview
The std::time module provides types for measuring time and durations. Instant measures elapsed time, while Duration represents a span of time.
Code Example
#![allow(unused)]
fn main() {
use std::time::{Duration, Instant};
use std::thread;
let start = Instant::now();
thread::sleep(Duration::from_millis(100));
let duration = start.elapsed();
println!("Slept for {:?}", duration);
}
Explanation
Instant::now(): Captures the current moment (monotonic clock)Duration::from_millis(): Creates a time span from millisecondsthread::sleep(): Pauses execution for a durationelapsed(): Calculates time passed since the instant
Key Concepts
Instant vs SystemTime
Instant: Monotonic clock, never goes backward, for measuring durationsSystemTime: System clock, can change, for wall-clock time
Duration Creation
#![allow(unused)]
fn main() {
Duration::from_secs(5)
Duration::from_millis(100)
Duration::from_micros(500)
Duration::from_nanos(1000)
}
Duration Operations
#![allow(unused)]
fn main() {
let d1 = Duration::from_secs(1);
let d2 = Duration::from_millis(500);
let total = d1 + d2; // 1.5 seconds
let diff = d1 - d2; // 0.5 seconds
}
Use Cases
- Performance Measurement: Benchmark code execution time
- Timeouts: Implement operation timeouts
- Rate Limiting: Control request rates
- Animations: Time-based animations and updates
- Delays: Pause execution between operations
Best Practices
✅ Do:
- Use
Instantfor measuring elapsed time - Use
SystemTimefor timestamps and dates - Check duration with
as_secs(),as_millis()for specific units
❌ Don’t:
- Compare
Instantfrom different systems - Use
SystemTimefor duration measurements (can go backward) - Create tight sleep loops (wastes CPU)
Common Patterns
Timeout Pattern
#![allow(unused)]
fn main() {
let timeout = Duration::from_secs(5);
let start = Instant::now();
while start.elapsed() < timeout {
// Try operation
if try_operation() {
break;
}
thread::sleep(Duration::from_millis(100));
}
}
Benchmarking Pattern
#![allow(unused)]
fn main() {
let start = Instant::now();
expensive_operation();
let duration = start.elapsed();
println!("Operation took: {:?}", duration);
}
Related Features
- Threading - Thread sleep operations
- Process Execution - Timing external processes
Learn More
Collections
Overview
Rust’s std::collections module provides essential data structures. HashMap stores key-value pairs with O(1) average lookup, while HashSet stores unique values.
Code Example
#![allow(unused)]
fn main() {
use std::collections::{HashMap, HashSet};
// HashMap example
let mut map = HashMap::new();
map.insert("key1", "value1");
map.insert("key2", "value2");
println!("HashMap: {:?}", map);
// HashSet example
let mut set = HashSet::new();
set.insert("item1");
set.insert("item2");
println!("HashSet: {:?}", set);
}
Explanation
HashMap
- Key-Value Storage: Fast lookups by key
- Generic Types:
HashMap<K, V>where K: Hash + Eq - Methods:
insert(),get(),remove(),contains_key()
HashSet
- Unique Elements: Automatically prevents duplicates
- Fast Lookups: O(1) average
contains()check - Based on HashMap: Essentially
HashMap<T, ()>
Key Concepts
HashMap Operations
#![allow(unused)]
fn main() {
let mut scores = HashMap::new();
// Insert
scores.insert("Blue", 10);
// Get (returns Option)
if let Some(score) = scores.get("Blue") {
println!("Score: {}", score);
}
// Update or insert
scores.entry("Blue").or_insert(50);
// Iterate
for (key, value) in &scores {
println!("{}: {}", key, value);
}
}
HashSet Operations
#![allow(unused)]
fn main() {
let mut set = HashSet::new();
// Insert (returns bool if new)
set.insert(1);
// Check membership
if set.contains(&1) {
println!("Has 1");
}
// Set operations
let set2 = HashSet::from([2, 3]);
let union: HashSet<_> = set.union(&set2).collect();
let intersection: HashSet<_> = set.intersection(&set2).collect();
}
Use Cases
HashMap
- Caching: Store computed results by key
- Indexing: Fast lookup tables
- Configuration: Key-value settings
- Counting: Word/item frequency counting
HashSet
- Deduplication: Remove duplicates from data
- Membership Testing: Fast “is this present?” checks
- Set Operations: Union, intersection, difference
- Visited Tracking: Graph algorithms, avoiding revisits
Best Practices
✅ Do:
- Preallocate capacity if size is known:
HashMap::with_capacity(n) - Use
entry()API for update-or-insert patterns - Consider
BTreeMap/BTreeSetfor sorted iteration
❌ Don’t:
- Use types without
Hash + Eqas HashMap keys - Modify keys after insertion (breaks invariants)
- Assume iteration order (use BTreeMap for sorted order)
Common Patterns
Frequency Counter
#![allow(unused)]
fn main() {
let mut freq = HashMap::new();
for word in text.split_whitespace() {
*freq.entry(word).or_insert(0) += 1;
}
}
Cache Pattern
#![allow(unused)]
fn main() {
let mut cache = HashMap::new();
let result = cache.entry(key)
.or_insert_with(|| expensive_computation(key));
}
Other Collections
- Vec: Dynamic array, contiguous memory
- VecDeque: Double-ended queue
- BTreeMap/BTreeSet: Sorted collections
- LinkedList: Doubly-linked list
Related Features
- Iterators - Iterating over collections
- Memory Operations - Capacity management
Learn More
File I/O
Overview
Rust’s std::fs and std::io modules provide safe, cross-platform file operations. Create, read, write, and manage files with explicit error handling.
Code Example
#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::{Write, BufReader, BufRead};
// Write to file
let file_path = "test_file.txt";
{
let mut file = File::create(file_path)
.expect("Unable to create file");
file.write_all(b"Hello, Rust!")
.expect("Unable to write to file");
}
// Read from file
let file = File::open(file_path)
.expect("Unable to open file");
let mut buf_reader = BufReader::new(file);
let mut line = String::new();
buf_reader.read_line(&mut line)
.expect("Unable to read line");
println!("Read from file: {}", line.trim());
// Clean up
std::fs::remove_file(file_path)
.expect("Unable to remove file");
}
Explanation
File::create(): Creates a new file (truncates if exists)File::open(): Opens existing file for readingwrite_all(): Writes entire byte buffer to fileBufReader: Buffered reading for better performanceread_line(): Reads until newline character
Key Concepts
File Opening Modes
#![allow(unused)]
fn main() {
use std::fs::OpenOptions;
// Read-only
let file = File::open("file.txt")?;
// Write (create or truncate)
let file = File::create("file.txt")?;
// Append
let file = OpenOptions::new()
.append(true)
.open("file.txt")?;
// Read and write
let file = OpenOptions::new()
.read(true)
.write(true)
.open("file.txt")?;
}
Buffered I/O
Always use buffered readers/writers for better performance:
#![allow(unused)]
fn main() {
use std::io::{BufReader, BufWriter};
// Buffered reading
let reader = BufReader::new(File::open("input.txt")?);
// Buffered writing
let writer = BufWriter::new(File::create("output.txt")?);
}
Error Handling
File operations return Result<T, std::io::Error>:
#![allow(unused)]
fn main() {
match File::open("file.txt") {
Ok(file) => { /* use file */ },
Err(e) => eprintln!("Error: {}", e),
}
}
Use Cases
- Configuration: Read/write config files
- Logging: Append to log files
- Data Processing: Read input, write output
- Caching: Store computed results to disk
- Reports: Generate text/CSV reports
Best Practices
✅ Do:
- Use
BufReader/BufWriterfor line-by-line or buffered I/O - Handle errors explicitly with
?ormatch - Use scopes
{}to ensure files close promptly - Use
std::fsconvenience functions when appropriate
❌ Don’t:
- Forget to handle file errors (use
?or.expect()) - Read entire large files into memory at once
- Keep files open longer than necessary
- Assume file exists without checking
Convenience Functions
#![allow(unused)]
fn main() {
use std::fs;
// Read entire file to string
let contents = fs::read_to_string("file.txt")?;
// Read entire file to bytes
let bytes = fs::read("file.bin")?;
// Write string to file
fs::write("file.txt", "content")?;
// Copy file
fs::copy("src.txt", "dst.txt")?;
// Remove file
fs::remove_file("file.txt")?;
}
Common Patterns
Read File Line by Line
#![allow(unused)]
fn main() {
use std::io::BufRead;
let file = File::open("file.txt")?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
println!("{}", line);
}
}
Write Multiple Lines
#![allow(unused)]
fn main() {
let mut file = File::create("output.txt")?;
for line in &lines {
writeln!(file, "{}", line)?;
}
}
Related Features
- Path Operations - Path manipulation and queries
- Error Handling - Handling I/O errors
Learn More
Path Operations
Overview
The std::path module provides cross-platform path manipulation. Path and PathBuf handle filesystem paths safely across Windows, Unix, and other platforms.
Code Example
#![allow(unused)]
fn main() {
use std::path::Path;
let path = Path::new("example.txt");
println!("Path exists: {}", path.exists());
println!("Path: {}", path.display());
}
Explanation
Path::new(): Creates a path reference from a stringexists(): Checks if path exists in filesystemdisplay(): Formats path for display (handles encoding)
Key Concepts
Path vs PathBuf
Path: Borrowed path slice (like&str)PathBuf: Owned path (likeString)
#![allow(unused)]
fn main() {
use std::path::{Path, PathBuf};
// Borrowed
let path: &Path = Path::new("/tmp/file.txt");
// Owned
let mut path_buf = PathBuf::from("/tmp");
path_buf.push("file.txt");
}
Path Components
#![allow(unused)]
fn main() {
let path = Path::new("/home/user/file.txt");
// File name
println!("{:?}", path.file_name()); // Some("file.txt")
// Extension
println!("{:?}", path.extension()); // Some("txt")
// Parent directory
println!("{:?}", path.parent()); // Some("/home/user")
// Components
for component in path.components() {
println!("{:?}", component);
}
}
Path Construction
#![allow(unused)]
fn main() {
let mut path = PathBuf::from("/home");
path.push("user");
path.push("file.txt");
// /home/user/file.txt
// Join paths
let path = Path::new("/home").join("user").join("file.txt");
}
Use Cases
- File Location: Build paths to files and directories
- Path Validation: Check if paths exist or are valid
- Path Parsing: Extract filename, extension, parent
- Cross-Platform: Write code that works on any OS
- Configuration: Handle user-provided paths safely
Best Practices
✅ Do:
- Use
PathandPathBufinstead of strings for paths - Use
join()to combine path components - Handle relative vs absolute paths appropriately
- Use
canonicalize()to resolve symlinks and..
❌ Don’t:
- Manually concatenate paths with
/or\\ - Assume path separators (use
std::path::MAIN_SEPARATOR) - Ignore path encoding issues (use
display()orto_string_lossy())
Common Operations
Check Path Type
#![allow(unused)]
fn main() {
use std::path::Path;
let path = Path::new("file.txt");
if path.is_file() {
println!("It's a file");
}
if path.is_dir() {
println!("It's a directory");
}
if path.is_symlink() {
println!("It's a symbolic link");
}
}
Path Manipulation
#![allow(unused)]
fn main() {
let path = PathBuf::from("/home/user/file.txt");
// Change extension
let new_path = path.with_extension("md");
// /home/user/file.md
// Change filename
let new_path = path.with_file_name("other.txt");
// /home/user/other.txt
// Remove filename
let mut path = path;
path.pop(); // Now: /home/user
}
Canonicalize Paths
#![allow(unused)]
fn main() {
// Resolve . and .. and symlinks
let canonical = Path::new("../file.txt").canonicalize()?;
println!("Absolute path: {}", canonical.display());
}
Common Patterns
Build Output Path
#![allow(unused)]
fn main() {
let input = Path::new("input.txt");
let output = input.with_extension("out");
}
Iterate Directory
#![allow(unused)]
fn main() {
use std::fs;
for entry in fs::read_dir(".")? {
let entry = entry?;
let path = entry.path();
println!("{}", path.display());
}
}
Safe Path Join
#![allow(unused)]
fn main() {
fn safe_join(base: &Path, user_path: &str) -> Option<PathBuf> {
let joined = base.join(user_path);
if joined.starts_with(base) {
Some(joined)
} else {
None // Prevent directory traversal
}
}
}
Related Features
- File I/O - Reading and writing files
- Environment Variables - Getting system paths
Learn More
Synchronization
Overview
Rust provides thread-safe primitives through Arc (Atomic Reference Counting) and Mutex (Mutual Exclusion). These enable safe shared state across threads.
Code Example
#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for i in 0..3 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
println!("Thread {} incremented counter to {}", i, *num);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final counter value: {}", *counter.lock().unwrap());
}
Explanation
Arc<T>: Atomic reference-counted pointer for shared ownershipMutex<T>: Mutual exclusion lock for interior mutabilityArc::clone(): Increments reference count (cheap operation)lock(): Acquires the mutex, blocks if held by another threadmove: Transfers ownership into the thread closure
Key Concepts
Arc - Shared Ownership
Arc<T> allows multiple owners of the same data:
#![allow(unused)]
fn main() {
let data = Arc::new(vec![1, 2, 3]);
let data_clone = Arc::clone(&data); // Same data, multiple owners
}
Thread-safe alternative to Rc<T> (single-threaded reference counting).
Mutex - Interior Mutability
Mutex<T> provides synchronized mutable access:
#![allow(unused)]
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
} // Lock automatically released
}
Combined Pattern
Arc<Mutex<T>> is the standard pattern for shared mutable state:
Arcfor sharing across threadsMutexfor safe mutation
Use Cases
- Shared Counters: Statistics, progress tracking
- Shared State: Configuration, caches shared across threads
- Thread Pools: Work queues accessed by multiple workers
- Resource Pools: Database connections, file handles
Best Practices
✅ Do:
- Keep critical sections (locked code) small
- Use
Arc::clone()explicitly for clarity - Consider
RwLockfor read-heavy workloads - Handle lock poisoning (when thread panics while holding lock)
❌ Don’t:
- Hold locks across await points (use async Mutex instead)
- Create deadlocks (acquire locks in consistent order)
- Clone Arc unnecessarily (passing
&Arcis fine) - Forget that
lock()can panic
Other Synchronization Primitives
RwLock
Read-write lock - multiple readers or one writer:
#![allow(unused)]
fn main() {
use std::sync::RwLock;
let lock = Arc::new(RwLock::new(5));
// Multiple readers
let r1 = lock.read().unwrap();
let r2 = lock.read().unwrap();
// One writer (exclusive)
let mut w = lock.write().unwrap();
*w += 1;
}
Channels
Message passing for thread communication:
#![allow(unused)]
fn main() {
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("Hello from thread").unwrap();
});
let msg = rx.recv().unwrap();
println!("{}", msg);
}
Atomic Types
Lock-free atomic operations:
#![allow(unused)]
fn main() {
use std::sync::atomic::{AtomicUsize, Ordering};
let counter = Arc::new(AtomicUsize::new(0));
counter.fetch_add(1, Ordering::SeqCst);
}
Common Patterns
Shared Resource Pool
#![allow(unused)]
fn main() {
let pool = Arc::new(Mutex::new(Vec::new()));
// Fill pool
for i in 0..10 {
pool.lock().unwrap().push(i);
}
// Multiple threads consume from pool
let mut handles = vec![];
for _ in 0..3 {
let pool = Arc::clone(&pool);
let handle = thread::spawn(move || {
while let Some(item) = pool.lock().unwrap().pop() {
// Process item
}
});
handles.push(handle);
}
}
Avoiding Deadlocks
#![allow(unused)]
fn main() {
// Bad: can deadlock
let lock1 = Arc::new(Mutex::new(0));
let lock2 = Arc::new(Mutex::new(0));
// Thread 1: lock1 -> lock2
// Thread 2: lock2 -> lock1 // DEADLOCK!
// Good: always acquire in same order
// Both threads: lock1 -> lock2
}
Related Features
- Threading - Creating and managing threads
- Rc and RefCell - Single-threaded alternatives
Learn More
Environment Variables
Overview
The std::env module provides access to environment variables and process information. Query, set, and iterate over environment variables safely.
Code Example
#![allow(unused)]
fn main() {
use std::env;
// Iterate over environment variables
for (key, value) in env::vars().take(3) {
println!("{}: {}", key, value);
}
}
Explanation
env::vars(): Returns iterator over all environment variablestake(3): Limits to first 3 variables- Returns
(String, String)tuples of key-value pairs
Key Concepts
Reading Variables
#![allow(unused)]
fn main() {
use std::env;
// Get specific variable (returns Result)
match env::var("HOME") {
Ok(val) => println!("HOME: {}", val),
Err(e) => println!("Couldn't read HOME: {}", e),
}
// Get with default
let editor = env::var("EDITOR").unwrap_or("vim".to_string());
// Get as OsString (non-UTF8 safe)
let path = env::var_os("PATH");
}
Setting Variables
#![allow(unused)]
fn main() {
use std::env;
// Set for current process and children
env::set_var("MY_VAR", "my_value");
// Remove variable
env::remove_var("MY_VAR");
}
⚠️ Note: Changes only affect current process and child processes, not parent.
Process Information
#![allow(unused)]
fn main() {
// Current directory
let current_dir = env::current_dir()?;
// Change directory
env::set_current_dir("/tmp")?;
// Command line arguments
let args: Vec<String> = env::args().collect();
// Current executable path
let exe = env::current_exe()?;
}
Use Cases
- Configuration: Read config from environment
- CI/CD: Detect CI environment, read secrets
- System Info: Get user home, temp directory
- Path Resolution: Find executables in PATH
- Feature Flags: Enable/disable features via env vars
Best Practices
✅ Do:
- Use
var_os()for paths (handles non-UTF8) - Provide defaults with
.unwrap_or()or.unwrap_or_else() - Document required environment variables
- Validate variable values after reading
❌ Don’t:
- Assume variables exist (always handle
Result) - Store sensitive data in environment (can leak in logs)
- Modify environment in library code (side effects)
- Use
unwrap()without good reason
Common Environment Variables
Unix/Linux
HOME: User’s home directoryUSER: Current usernamePATH: Executable search pathSHELL: User’s shellLANG: Locale settings
Windows
USERPROFILE: User’s profile directoryUSERNAME: Current usernamePATH: Executable search pathTEMP/TMP: Temporary directory
Cross-Platform
#![allow(unused)]
fn main() {
use std::env;
fn get_home_dir() -> Option<PathBuf> {
env::var_os("HOME")
.or_else(|| env::var_os("USERPROFILE"))
.map(PathBuf::from)
}
}
Common Patterns
Configuration Loading
#![allow(unused)]
fn main() {
fn load_config() -> Config {
Config {
db_url: env::var("DATABASE_URL")
.expect("DATABASE_URL must be set"),
port: env::var("PORT")
.unwrap_or("8080".to_string())
.parse()
.expect("PORT must be a number"),
debug: env::var("DEBUG").is_ok(),
}
}
}
PATH Parsing
#![allow(unused)]
fn main() {
use std::env;
fn find_in_path(name: &str) -> Option<PathBuf> {
env::var_os("PATH")?
.split(|c| c == ':' || c == ';')
.map(PathBuf::from)
.find_map(|dir| {
let path = dir.join(name);
if path.exists() {
Some(path)
} else {
None
}
})
}
}
Temporary Directory
#![allow(unused)]
fn main() {
use std::env;
let temp_dir = env::temp_dir();
let temp_file = temp_dir.join("myapp_temp.txt");
}
Command Line Arguments
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("Program: {}", args[0]);
if args.len() > 1 {
println!("First argument: {}", args[1]);
}
// Skip program name
for arg in env::args().skip(1) {
println!("Arg: {}", arg);
}
}
Related Features
- Path Operations - Working with filesystem paths
- Process Execution - Spawning child processes
Learn More
Process Execution
Overview
The std::process module enables spawning and interacting with child processes. Run external commands, capture output, and manage subprocesses safely.
Code Example
#![allow(unused)]
fn main() {
use std::process::Command;
let output = Command::new("echo")
.arg("Hello from subprocess")
.output()
.expect("Failed to execute command");
println!("Command output: {}",
String::from_utf8_lossy(&output.stdout).trim());
}
Explanation
Command::new(): Creates a new command to executearg(): Adds command-line argumentoutput(): Runs command and waits, capturing outputString::from_utf8_lossy(): Converts bytes to string safely
Key Concepts
Building Commands
#![allow(unused)]
fn main() {
use std::process::Command;
let output = Command::new("ls")
.arg("-l")
.arg("-a")
.current_dir("/tmp")
.env("MY_VAR", "value")
.output()?;
}
Execution Methods
#![allow(unused)]
fn main() {
// Capture output (wait for completion)
let output = Command::new("echo").arg("test").output()?;
// Wait for exit status only
let status = Command::new("sleep").arg("1").status()?;
// Stream output (inherit stdio)
let status = Command::new("cargo")
.arg("build")
.status()?;
// Full control with spawn
let mut child = Command::new("long-running")
.spawn()?;
let status = child.wait()?;
}
Output Structure
#![allow(unused)]
fn main() {
pub struct Output {
pub status: ExitStatus,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
// Check success
if output.status.success() {
println!("Command succeeded");
}
// Get exit code
if let Some(code) = output.status.code() {
println!("Exit code: {}", code);
}
}
Use Cases
- Build Tools: Run compilers, bundlers
- System Administration: Execute system commands
- Testing: Run test executables, check behavior
- Pipeline Processing: Chain commands together
- Integration: Interface with external tools
Best Practices
✅ Do:
- Check exit status before using output
- Use
.status()when output not needed (more efficient) - Properly escape/validate user input in commands
- Handle both stdout and stderr
❌ Don’t:
- Ignore command errors
- Use shell interpolation with untrusted input (security risk)
- Assume command exists on all platforms
- Forget to read/consume child process output (can deadlock)
Advanced Usage
Piping Between Commands
#![allow(unused)]
fn main() {
use std::process::{Command, Stdio};
let ps_child = Command::new("ps")
.arg("aux")
.stdout(Stdio::piped())
.spawn()?;
let grep_output = Command::new("grep")
.arg("rust")
.stdin(ps_child.stdout.unwrap())
.output()?;
}
Streaming Output
#![allow(unused)]
fn main() {
use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio};
let mut child = Command::new("cargo")
.arg("build")
.stdout(Stdio::piped())
.spawn()?;
if let Some(stdout) = child.stdout.take() {
let reader = BufReader::new(stdout);
for line in reader.lines() {
println!("Build: {}", line?);
}
}
child.wait()?;
}
Timeout Implementation
#![allow(unused)]
fn main() {
use std::time::Duration;
use std::thread;
let mut child = Command::new("long-process").spawn()?;
let timeout = Duration::from_secs(5);
thread::sleep(timeout);
// Kill if still running
match child.try_wait()? {
Some(status) => println!("Exited with: {}", status),
None => {
child.kill()?;
println!("Process killed (timeout)");
}
}
}
Platform Considerations
Unix-Specific
#![allow(unused)]
fn main() {
#[cfg(unix)]
use std::os::unix::process::CommandExt;
#[cfg(unix)]
let command = Command::new("ls")
.uid(1000) // Set user ID
.gid(1000); // Set group ID
}
Windows-Specific
#![allow(unused)]
fn main() {
#[cfg(windows)]
use std::os::windows::process::CommandExt;
#[cfg(windows)]
let command = Command::new("cmd")
.creation_flags(0x08000000); // CREATE_NO_WINDOW
}
Common Patterns
Check Tool Availability
#![allow(unused)]
fn main() {
fn has_command(cmd: &str) -> bool {
Command::new(cmd)
.arg("--version")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
}
Run with Error Context
#![allow(unused)]
fn main() {
fn run_command(cmd: &str, args: &[&str]) -> Result<String, String> {
let output = Command::new(cmd)
.args(args)
.output()
.map_err(|e| format!("Failed to execute {}: {}", cmd, e))?;
if !output.status.success() {
return Err(format!(
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
}
Security Considerations
⚠️ Never construct commands with untrusted input:
#![allow(unused)]
fn main() {
// BAD - shell injection vulnerability
Command::new("sh")
.arg("-c")
.arg(format!("ls {}", user_input));
// GOOD - arguments properly escaped
Command::new("ls")
.arg(user_input);
}
Related Features
- Environment Variables - Setting process environment
- Error Handling - Handling command errors
Learn More
Error Handling
Overview
Rust uses the Result<T, E> type for recoverable errors. This explicit error handling prevents forgotten error checks and makes error paths clear.
Code Example
#![allow(unused)]
fn main() {
let result: Result<i32, &str> = Ok(42);
match result {
Ok(value) => println!("Success: {}", value),
Err(e) => println!("Error: {}", e),
}
}
Explanation
Result<T, E>: Enum withOk(T)for success orErr(E)for failurematch: Pattern matching to handle both cases- Forces explicit handling of success and failure paths
Key Concepts
Result Type
#![allow(unused)]
fn main() {
enum Result<T, E> {
Ok(T), // Success value
Err(E), // Error value
}
}
Error Propagation with ?
The ? operator propagates errors automatically:
#![allow(unused)]
fn main() {
fn read_file() -> Result<String, std::io::Error> {
let mut file = File::open("config.txt")?; // Returns early if Err
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
}
Common Result Methods
#![allow(unused)]
fn main() {
let result: Result<i32, &str> = Ok(42);
// Unwrap (panics on Err)
let value = result.unwrap();
// Unwrap with message
let value = result.expect("Failed to get value");
// Unwrap or default
let value = result.unwrap_or(0);
// Unwrap or compute default
let value = result.unwrap_or_else(|_| 0);
// Check if Ok/Err
if result.is_ok() { }
if result.is_err() { }
// Map success value
let doubled = result.map(|x| x * 2);
// Map error value
let result = result.map_err(|e| format!("Error: {}", e));
}
Use Cases
- I/O Operations: File, network operations that can fail
- Parsing: Converting strings to numbers, dates, etc.
- Validation: Checking user input, business rules
- API Calls: HTTP requests, database queries
- Resource Acquisition: Opening files, connections
Best Practices
✅ Do:
- Use
?for error propagation in functions returning Result - Provide context with
.map_err()or custom error types - Use
expect()only when failure is truly unexpected - Return
Resultfrom functions that can fail
❌ Don’t:
- Use
unwrap()in production code (use?orexpect()) - Ignore
Resultvalues (compiler warns about this) - Create errors for control flow (use Option for that)
- Swallow errors without handling them
Error Types
Standard Library Errors
#![allow(unused)]
fn main() {
// I/O errors
fn read_file() -> Result<String, std::io::Error> { }
// Parse errors
fn parse_num(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse()
}
// Generic string errors
fn validate(s: &str) -> Result<(), String> {
if s.is_empty() {
Err("String is empty".to_string())
} else {
Ok(())
}
}
}
Custom Error Types
#![allow(unused)]
fn main() {
#[derive(Debug)]
enum MyError {
IoError(std::io::Error),
ParseError(String),
NotFound,
}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
MyError::IoError(e) => write!(f, "I/O error: {}", e),
MyError::ParseError(msg) => write!(f, "Parse error: {}", msg),
MyError::NotFound => write!(f, "Not found"),
}
}
}
impl std::error::Error for MyError {}
}
Common Patterns
Multiple Error Types
#![allow(unused)]
fn main() {
// Convert between error types
fn process() -> Result<i32, String> {
let file = File::open("num.txt")
.map_err(|e| format!("Failed to open file: {}", e))?;
let num: i32 = "123".parse()
.map_err(|e| format!("Failed to parse: {}", e))?;
Ok(num)
}
}
Early Return Pattern
#![allow(unused)]
fn main() {
fn validate_user(user: &User) -> Result<(), String> {
if user.name.is_empty() {
return Err("Name cannot be empty".to_string());
}
if user.age < 18 {
return Err("User must be 18+".to_string());
}
Ok(())
}
}
Collecting Results
#![allow(unused)]
fn main() {
// Stop at first error
let results: Result<Vec<i32>, _> = vec!["1", "2", "3"]
.iter()
.map(|s| s.parse::<i32>())
.collect();
// Partition successes and failures
let (oks, errs): (Vec<_>, Vec<_>) = results
.into_iter()
.partition(Result::is_ok);
}
Fallible Iterator
#![allow(unused)]
fn main() {
// Process items, stop on first error
fn process_all(items: &[&str]) -> Result<(), String> {
for item in items {
process_item(item)?; // Stops on first error
}
Ok(())
}
}
Result vs Panic
Use Result for:
- Expected, recoverable errors
- External I/O operations
- User input validation
- Business logic errors
Use panic! for:
- Programming bugs (assertions)
- Invariant violations
- Unrecoverable errors
- Prototyping
Related Features
- Option Type - Handling absence of values
- Panic Handling - Recovering from panics
Learn More
Option Type
Overview
The Option<T> type represents an optional value: either Some(T) containing a value, or None representing absence. This eliminates null pointer errors at compile time.
Code Example
#![allow(unused)]
fn main() {
let some_value: Option<i32> = Some(5);
let none_value: Option<i32> = None;
println!("Some value: {:?}", some_value);
println!("None value: {:?}", none_value);
if let Some(val) = some_value {
println!("Unwrapped some value: {}", val);
}
}
Explanation
Option<T>: Enum that can beSome(T)orNoneif let: Pattern matching syntax for conditional unwrapping- No null pointers - absence is explicitly represented by
None
Key Concepts
Option Definition
#![allow(unused)]
fn main() {
enum Option<T> {
Some(T), // Contains a value
None, // No value
}
}
Pattern Matching
#![allow(unused)]
fn main() {
let x: Option<i32> = Some(5);
match x {
Some(val) => println!("Got: {}", val),
None => println!("No value"),
}
}
Common Methods
#![allow(unused)]
fn main() {
let x = Some(5);
// Unwrap (panics if None)
let val = x.unwrap();
// Unwrap with message
let val = x.expect("Value required");
// Unwrap or default
let val = x.unwrap_or(0);
// Unwrap or compute default
let val = x.unwrap_or_else(|| 0);
// Check if Some/None
if x.is_some() { }
if x.is_none() { }
// Map value if Some
let doubled = x.map(|n| n * 2);
// And then (flatMap)
let result = x.and_then(|n| Some(n * 2));
// Filter
let filtered = x.filter(|&n| n > 3);
// Take value, leave None
let taken = x.take();
}
Use Cases
- Optional Fields: Struct fields that may not be present
- Search Results: Finding items that may not exist
- Configuration: Optional settings with defaults
- Nullable Return Values: Functions that may not return a value
- Partial Data: Data that may be incomplete
Best Practices
✅ Do:
- Use
Optioninstead of sentinel values (like -1, null) - Use
?operator with Option in functions returning Option - Provide defaults with
unwrap_or()orunwrap_or_else() - Use
if letormatchfor clear intent
❌ Don’t:
- Use
unwrap()without good reason (prefer?or explicit handling) - Return
Option<bool>(use Result or custom enum instead) - Overuse
is_some()checks (prefer pattern matching)
Common Patterns
Chaining Operations
#![allow(unused)]
fn main() {
fn find_user_city(user_id: i32) -> Option<String> {
find_user(user_id)
.and_then(|user| user.address)
.map(|addr| addr.city)
}
}
Optional with Default
#![allow(unused)]
fn main() {
let config = Config {
timeout: config_value.unwrap_or(30),
retries: config_value.unwrap_or_default(),
};
}
Option in Structs
#![allow(unused)]
fn main() {
struct User {
name: String,
email: Option<String>, // Email is optional
phone: Option<String>,
}
impl User {
fn contact_info(&self) -> String {
self.email.as_ref()
.or(self.phone.as_ref())
.map(|s| s.as_str())
.unwrap_or("No contact info")
.to_string()
}
}
}
Iterator Methods Returning Option
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Find first matching element
let first_even = numbers.iter().find(|&&x| x % 2 == 0);
// Get first/last element
let first = numbers.first(); // Option<&i32>
let last = numbers.last();
// Get by index
let third = numbers.get(2); // Option<&i32>
}
Converting Option to Result
#![allow(unused)]
fn main() {
let opt: Option<i32> = Some(5);
// Convert to Result
let result: Result<i32, &str> = opt.ok_or("No value found");
// With computed error
let result = opt.ok_or_else(|| "No value found");
}
Early Return with ?
#![allow(unused)]
fn main() {
fn get_config_value(key: &str) -> Option<String> {
let config = load_config()?; // Return None if load fails
let value = config.get(key)?; // Return None if key missing
Some(value.clone())
}
}
Option vs Result
Use Option when:
- Value may or may not be present
- Absence is not an error (just empty)
- No additional error information needed
Use Result when:
- Operation can fail with specific errors
- Need to know why operation failed
- Error information is important for debugging
Advanced Techniques
Option Combinators
#![allow(unused)]
fn main() {
let x = Some(5);
let y = Some(10);
// Combine two Options
let sum = x.and_then(|a| y.map(|b| a + b));
// Choose first Some
let first = x.or(y);
// XOR (one but not both)
let xor = x.xor(y);
}
Transpose (Option ⟷ Result
#![allow(unused)]
fn main() {
let x: Option<Result<i32, &str>> = Some(Ok(5));
let y: Result<Option<i32>, &str> = x.transpose();
}
Flattening Nested Options
#![allow(unused)]
fn main() {
let nested: Option<Option<i32>> = Some(Some(5));
let flattened: Option<i32> = nested.flatten();
}
Related Features
- Error Handling - Using Result for errors
- Collections - Methods returning Option
Learn More
Network Operations
Overview
The std::net module provides TCP and UDP networking primitives. Create servers and clients for network communication with a simple, synchronous API.
Code Example
#![allow(unused)]
fn main() {
use std::net::TcpListener;
// Simple TCP listener demonstration
let listener_result = TcpListener::bind("127.0.0.1:0");
match listener_result {
Ok(listener) => {
let local_addr = listener.local_addr().unwrap();
println!("TCP listener bound to: {}", local_addr);
}
Err(e) => println!("TCP bind failed: {}", e),
}
}
Explanation
TcpListener::bind(): Binds to IP and port for incoming connections- Port
0: OS assigns random available port local_addr(): Gets the actual address bound to- Returns
Resultfor error handling
Key Concepts
TCP Server
#![allow(unused)]
fn main() {
use std::net::TcpListener;
use std::io::{Read, Write};
let listener = TcpListener::bind("127.0.0.1:8080")?;
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
let mut buffer = [0; 512];
stream.read(&mut buffer)?;
stream.write(b"HTTP/1.1 200 OK\r\n\r\n")?;
}
Err(e) => eprintln!("Connection failed: {}", e),
}
}
}
TCP Client
#![allow(unused)]
fn main() {
use std::net::TcpStream;
use std::io::{Read, Write};
let mut stream = TcpStream::connect("example.com:80")?;
stream.write(b"GET / HTTP/1.0\r\n\r\n")?;
let mut response = String::new();
stream.read_to_string(&mut response)?;
println!("Response: {}", response);
}
UDP Sockets
#![allow(unused)]
fn main() {
use std::net::UdpSocket;
let socket = UdpSocket::bind("127.0.0.1:0")?;
socket.send_to(b"Hello", "127.0.0.1:8080")?;
let mut buf = [0; 512];
let (amt, src) = socket.recv_from(&mut buf)?;
println!("Received {} bytes from {}", amt, src);
}
Use Cases
- Web Servers: HTTP/HTTPS servers
- APIs: REST or RPC services
- Chat Applications: Real-time messaging
- Game Servers: Multiplayer games
- IoT: Device communication
- Microservices: Service-to-service communication
Best Practices
✅ Do:
- Use
0.0.0.0for public servers,127.0.0.1for local only - Set timeouts with
set_read_timeout(),set_write_timeout() - Use thread pools or async for handling multiple connections
- Handle partial reads/writes in loops
❌ Don’t:
- Block indefinitely without timeouts
- Forget to handle connection errors
- Create thread per connection (use thread pools)
- Assume all data arrives in one read
Advanced Patterns
Threaded Server
#![allow(unused)]
fn main() {
use std::net::TcpListener;
use std::thread;
let listener = TcpListener::bind("127.0.0.1:8080")?;
for stream in listener.incoming() {
match stream {
Ok(stream) => {
thread::spawn(|| {
handle_client(stream);
});
}
Err(e) => eprintln!("Error: {}", e),
}
}
}
Connection Timeouts
#![allow(unused)]
fn main() {
use std::time::Duration;
use std::net::TcpStream;
let mut stream = TcpStream::connect("example.com:80")?;
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
stream.set_write_timeout(Some(Duration::from_secs(5)))?;
}
Non-Blocking I/O
#![allow(unused)]
fn main() {
use std::net::TcpStream;
let stream = TcpStream::connect("127.0.0.1:8080")?;
stream.set_nonblocking(true)?;
// Now read/write operations return immediately
// with WouldBlock error if not ready
}
Protocol Implementation Example
Simple Echo Server
use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::thread;
fn handle_client(mut stream: TcpStream) -> std::io::Result<()> {
let mut buffer = [0; 512];
loop {
let n = stream.read(&mut buffer)?;
if n == 0 {
return Ok(()); // Connection closed
}
stream.write_all(&buffer[..n])?;
}
}
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080")?;
println!("Echo server listening on port 8080");
for stream in listener.incoming() {
match stream {
Ok(stream) => {
thread::spawn(|| {
if let Err(e) = handle_client(stream) {
eprintln!("Error handling client: {}", e);
}
});
}
Err(e) => eprintln!("Connection error: {}", e),
}
}
Ok(())
}
IPv4 vs IPv6
#![allow(unused)]
fn main() {
use std::net::{TcpListener, SocketAddr};
// IPv4
let v4 = TcpListener::bind("127.0.0.1:8080")?;
// IPv6
let v6 = TcpListener::bind("[::1]:8080")?;
// Both (dual-stack)
let addr: SocketAddr = "[::]:8080".parse()?;
let dual = TcpListener::bind(addr)?;
}
Async Alternatives
For production use, consider async runtimes:
- tokio: Most popular async runtime
- async-std: Async version of std
- smol: Lightweight async runtime
// Example with tokio (not std)
// #[tokio::main]
// async fn main() {
// let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
// // ...
// }
Related Features
- Threading - Multi-threaded servers
- Error Handling - Handling network errors
Learn More
Iterators
Overview
Iterators are Rust’s primary tool for working with sequences. They enable functional programming patterns with methods like map, filter, and fold.
Code Example
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter()
.map(|x| x * 2)
.filter(|x| x > &3)
.collect();
println!("Original: {:?}", numbers);
println!("Doubled and filtered (>3): {:?}", doubled);
}
Explanation
iter(): Creates iterator over immutable referencesmap(): Transforms each elementfilter(): Keeps elements matching predicatecollect(): Consumes iterator into collection
Key Concepts
Iterator Types
#![allow(unused)]
fn main() {
let vec = vec![1, 2, 3];
let iter = vec.iter(); // &T
let iter_mut = vec.iter_mut(); // &mut T
let into_iter = vec.into_iter(); // T (consumes vec)
}
Common Methods
#![allow(unused)]
fn main() {
// Transforming
.map(|x| x * 2)
.filter(|x| x > &0)
.filter_map(|x| Some(x))
.flat_map(|x| vec![x, x])
// Aggregating
.sum(), .product()
.min(), .max()
.count()
.fold(0, |acc, x| acc + x)
.reduce(|acc, x| acc + x)
// Taking/Skipping
.take(5)
.skip(2)
.take_while(|x| x < &10)
.skip_while(|x| x < &5)
// Finding
.find(|x| x == &3)
.position(|x| x == &3)
.any(|x| x > &5)
.all(|x| x > &0)
}
Use Cases
- Data Transformation: Process collections functionally
- Lazy Evaluation: Compute only what’s needed
- Chaining Operations: Compose operations clearly
- Infinite Sequences: Generate unlimited data on-demand
Best Practices
✅ Do:
- Chain operations for clarity
- Use iterator methods instead of manual loops
- Leverage lazy evaluation for efficiency
- Use
collect()to convert to collections
❌ Don’t:
- Collect unnecessarily (stay in iterator chain)
- Use
unwrap()in iterator chains without good reason - Forget iterators are lazy (must consume them)
Related Features
- Collections - Data structures to iterate over
Learn More
Custom Formatting
Overview
The std::fmt module allows custom display formatting by implementing the Display and Debug traits.
Code Example
#![allow(unused)]
fn main() {
use std::fmt;
struct Point {
x: i32,
y: i32,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Point({}, {})", self.x, self.y)
}
}
let point = Point { x: 10, y: 20 };
println!("Custom formatted point: {}", point);
}
Key Concepts
Display: For user-facing output ({})Debug: For programmer-facing output ({:?})- Implement traits to control formatting
Learn More
Memory Operations
Overview
The std::mem module provides functions for memory inspection and manipulation.
Code Example
#![allow(unused)]
fn main() {
use std::mem;
let value = 42;
let size = mem::size_of_val(&value);
let align = mem::align_of_val(&value);
println!("Value: {}, Size: {} bytes, Alignment: {} bytes", value, size, align);
let mut data = vec![1, 2, 3];
let capacity_before = data.capacity();
data.reserve(10);
let capacity_after = data.capacity();
println!("Capacity before: {}, after: {}", capacity_before, capacity_after);
}
Key Concepts
size_of(): Get type/value size in bytesalign_of(): Get type/value alignmentdrop(): Manually drop a valueswap(): Swap two mutable locations
Learn More
Operator Overloading
Overview
Rust allows operator overloading through trait implementation (Add, Sub, Mul, etc.).
Code Example
#![allow(unused)]
fn main() {
use std::ops::Add;
#[derive(Debug, Copy, Clone)]
struct Complex {
real: f64,
imag: f64,
}
impl Add for Complex {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
real: self.real + other.real,
imag: self.imag + other.imag,
}
}
}
let c1 = Complex { real: 1.0, imag: 2.0 };
let c2 = Complex { real: 3.0, imag: 4.0 };
let c3 = c1 + c2;
println!("Complex addition: {:?} + {:?} = {:?}", c1, c2, c3);
}
Key Concepts
- Implement operator traits to enable custom operators
- Supports:
+,-,*,/,%,&,|,^,<<,>>, etc. - Can implement for assignment operators too
Learn More
Pin and PhantomPinned
Overview
Pin<P> prevents moving of a value in memory, enabling self-referential structs and safe async implementations.
Code Example
#![allow(unused)]
fn main() {
use std::pin::Pin;
use std::marker::PhantomPinned;
struct SelfReferential {
data: String,
data_ptr: *const String,
_pin: PhantomPinned,
}
impl SelfReferential {
fn new(data: String) -> Pin<Box<Self>> {
let mut boxed = Box::pin(SelfReferential {
data,
data_ptr: std::ptr::null(),
_pin: PhantomPinned,
});
let data_ptr = &boxed.data as *const String;
unsafe {
let mut_ref = Pin::as_mut(&mut boxed);
Pin::get_unchecked_mut(mut_ref).data_ptr = data_ptr;
}
boxed
}
fn get_data(&self) -> &str {
&self.data
}
}
let pinned = SelfReferential::new("Pinned data".to_string());
println!("Pinned data: {}", pinned.get_data());
}
Key Concepts
- Pin: Guarantees value won’t move in memory
- PhantomPinned: Marks type as !Unpin
- Essential for self-referential structs and async/await
Learn More
Future and Async
Overview
The Future trait is the foundation of Rust’s async/await system, representing values that may not be ready yet.
Code Example
#![allow(unused)]
fn main() {
use std::future::Future;
use std::task::{Context, Poll, Waker};
use std::pin::Pin;
struct SimpleFuture {
value: i32,
}
impl Future for SimpleFuture {
type Output = i32;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(self.value)
}
}
let future = SimpleFuture { value: 42 };
let mut boxed_future = Box::pin(future);
// Create a dummy waker for polling
struct NoOpWaker;
impl std::task::Wake for NoOpWaker {
fn wake(self: std::sync::Arc<Self>) {}
}
let waker: Waker = std::sync::Arc::new(NoOpWaker).into();
let mut cx = Context::from_waker(&waker);
match boxed_future.as_mut().poll(&mut cx) {
Poll::Ready(value) => println!("Future resolved with value: {}", value),
Poll::Pending => println!("Future is pending"),
}
}
Key Concepts
- Future: Async computation that may not be complete
- Poll: Check if future is ready
- Async/await: Built on top of Future trait
Learn More
Rc and RefCell
Overview
Rc<T> provides shared ownership, while RefCell<T> enables interior mutability with runtime borrow checking.
Code Example
#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::cell::RefCell;
let shared_data = Rc::new(RefCell::new(vec![1, 2, 3]));
// Clone the Rc to show shared ownership
let shared_clone = Rc::clone(&shared_data);
// Modify through RefCell
shared_data.borrow_mut().push(4);
println!("Original Rc count: {}", Rc::strong_count(&shared_data));
println!("Shared data: {:?}", shared_data.borrow());
println!("Clone also sees: {:?}", shared_clone.borrow());
}
Key Concepts
- Rc: Reference counting for shared ownership (single-threaded)
- RefCell: Runtime-checked interior mutability
- Use
Arc<Mutex<T>>for multi-threaded version
Learn More
Any Type
Overview
The Any trait enables runtime type inspection and downcasting for dynamic typing scenarios.
Code Example
#![allow(unused)]
fn main() {
use std::any::Any;
let string_value: &dyn Any = &"Hello, Any!".to_string();
let int_value: &dyn Any = &42i32;
if let Some(s) = string_value.downcast_ref::<String>() {
println!("String value: {}", s);
}
if let Some(i) = int_value.downcast_ref::<i32>() {
println!("Integer value: {}", i);
}
}
Key Concepts
- Any: Trait for runtime type information
- downcast_ref: Safely convert to concrete type
- Useful for plugin systems, dynamic dispatch
Learn More
Panic Handling
Overview
Rust’s panic mechanism handles unrecoverable errors. catch_unwind allows controlled panic recovery.
Code Example
#![allow(unused)]
fn main() {
use std::panic;
let result = panic::catch_unwind(|| {
println!("About to panic...");
panic!("This is a controlled panic!");
});
match result {
Ok(_) => println!("No panic occurred"),
Err(e) => {
if let Some(s) = e.downcast_ref::<&str>() {
println!("Caught panic: {}", s);
} else {
println!("Caught unknown panic");
}
}
}
}
Key Concepts
- panic!: Unrecoverable error, unwinds stack
- catch_unwind: Recovers from panics
- Use Result for recoverable errors, panic for bugs
Learn More
Contributing
Thank you for your interest in contributing to the Rust Standard Library Showcase! 🦀
How to Contribute
Types of Contributions
- Bug Fixes: Found an error? Help us fix it!
- Documentation Improvements: Better explanations or examples
- New Features: Additional stdlib features to showcase
- Code Quality: Improved patterns or best practices
Getting Started
- Fork the repository on GitHub
- Clone your fork locally
- Create a feature branch:
git checkout -b my-feature - Make your changes
- Test your changes:
cargo build && cargo run - Commit with clear messages
- Push to your fork
- Open a Pull Request
Code Style
- Follow Rust conventions (use
rustfmt) - Add clear comments for complex logic
- Update documentation if needed
- Keep examples simple and educational
Documentation
This documentation is built with mdBook. To build locally:
cd docs
mdbook serve
Then open http://localhost:3000 in your browser.
Pull Request Guidelines
- Keep PRs focused on a single change
- Describe what and why in the PR description
- Be responsive to feedback
- Ensure all tests pass
Code of Conduct
This project follows the Rust Code of Conduct. Please be respectful and inclusive.
Questions?
- Open an issue for questions or discussions
- Check existing issues before creating new ones
Thank you for helping make Rust education better! 🚀