Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Sequential Learning: Start with Basic Features and progress to Advanced
  2. Reference: Jump to specific features as needed
  3. Hands-On: Run the actual code from the repository
  4. 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 closure
  • thread::current(): Gets a handle to the current thread
  • join(): 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 threads
  • Sync - 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::Builder for 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)

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 milliseconds
  • thread::sleep(): Pauses execution for a duration
  • elapsed(): Calculates time passed since the instant

Key Concepts

Instant vs SystemTime

  • Instant: Monotonic clock, never goes backward, for measuring durations
  • SystemTime: 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 Instant for measuring elapsed time
  • Use SystemTime for timestamps and dates
  • Check duration with as_secs(), as_millis() for specific units

Don’t:

  • Compare Instant from different systems
  • Use SystemTime for 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);
}

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/BTreeSet for sorted iteration

Don’t:

  • Use types without Hash + Eq as 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

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 reading
  • write_all(): Writes entire byte buffer to file
  • BufReader: Buffered reading for better performance
  • read_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/BufWriter for line-by-line or buffered I/O
  • Handle errors explicitly with ? or match
  • Use scopes {} to ensure files close promptly
  • Use std::fs convenience 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)?;
}
}

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 string
  • exists(): Checks if path exists in filesystem
  • display(): Formats path for display (handles encoding)

Key Concepts

Path vs PathBuf

  • Path: Borrowed path slice (like &str)
  • PathBuf: Owned path (like String)
#![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 Path and PathBuf instead 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() or to_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
    }
}
}

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 ownership
  • Mutex<T>: Mutual exclusion lock for interior mutability
  • Arc::clone(): Increments reference count (cheap operation)
  • lock(): Acquires the mutex, blocks if held by another thread
  • move: 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:

  • Arc for sharing across threads
  • Mutex for 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 RwLock for 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 &Arc is 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
}

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 variables
  • take(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 directory
  • USER: Current username
  • PATH: Executable search path
  • SHELL: User’s shell
  • LANG: Locale settings

Windows

  • USERPROFILE: User’s profile directory
  • USERNAME: Current username
  • PATH: Executable search path
  • TEMP/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);
    }
}

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 execute
  • arg(): Adds command-line argument
  • output(): Runs command and waits, capturing output
  • String::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);
}

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 with Ok(T) for success or Err(E) for failure
  • match: 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 Result from functions that can fail

Don’t:

  • Use unwrap() in production code (use ? or expect())
  • Ignore Result values (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

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 be Some(T) or None
  • if 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 Option instead of sentinel values (like -1, null)
  • Use ? operator with Option in functions returning Option
  • Provide defaults with unwrap_or() or unwrap_or_else()
  • Use if let or match for 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();
}

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 Result for 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.0 for public servers, 127.0.0.1 for 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?;
//     // ...
// }

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 references
  • map(): Transforms each element
  • filter(): Keeps elements matching predicate
  • collect(): 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)

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 bytes
  • align_of(): Get type/value alignment
  • drop(): Manually drop a value
  • swap(): 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

  1. Fork the repository on GitHub
  2. Clone your fork locally
  3. Create a feature branch: git checkout -b my-feature
  4. Make your changes
  5. Test your changes: cargo build && cargo run
  6. Commit with clear messages
  7. Push to your fork
  8. 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! 🚀