Skip to content

Rust

Rust's design philosophy revolves around three core pillars: performance, reliability, and productivity. It achieves performance comparable to C or C++ by compiling to native code with zero-cost abstractions—meaning high-level features don't incur runtime penalties. Reliability comes from its ownership model, which eliminates issues like null pointer dereferences, data races, and buffer overflows at compile time. Productivity is enhanced through expressive syntax, excellent documentation, and integrated tools like Cargo for package management. Unlike languages with garbage collection (e.g., Java or Go), Rust manages memory manually but safely, avoiding the pauses and overhead associated with GC. This makes it ideal for resource-constrained environments while still being accessible to developers from higher-level languages.

Key features:

  • Memory safety: Compile-time guarantees prevent common bugs (use-after-free, double-free, data races).
  • Zero-cost abstractions: High-level code compiles to efficient machine code.
  • Ownership system: Unique approach to memory management without garbage collection.
  • Pattern matching: Powerful match expressions for control flow.
  • Trait system: Flexible type system similar to type classes or interfaces.
  • Cargo: Built-in package manager and build system.
  • Cross-platform: Compiles to native code for many targets.

Variables and Data Types

Variables in Rust are immutable by default. Use let mut for mutability. Types are inferred but can be explicit.

Data Type Description Example
i8, i16, i32, i64, i128 Signed integers let x: i32 = 42;
u8, u16, u32, u64, u128 Unsigned integers let y: u32 = 100;
isize, usize Pointer-sized integers let z: usize = 0;
f32, f64 Floating-point let pi: f64 = 3.14;
bool Boolean let flag: bool = true;
char Unicode scalar (4 bytes) let c: char = '🦀';
&str String slice (immutable) let s: &str = "hello";
String Owned, growable string let s = String::from("hello");
[T; N] Fixed-size array let arr: [i32; 3] = [1, 2, 3];
Vec<T> Dynamic vector let vec = vec![1, 2, 3];
(&T, &U) Tuple let tup: (i32, f64) = (5, 3.14);

Operations:

  • Arithmetic: +, -, *, /, % (modulo).
  • Comparison: ==, !=, <, >, <=, >=.
  • Logical: &&, ||, !.
  • Bitwise: &, |, ^, <<, >>.

Control Structures

  • If-Else:

    let x = 5;
    if x > 0 {
        println!("Positive");
    } else if x < 0 {
        println!("Negative");
    } else {
        println!("Zero");
    }
    
    // If as expression
    let result = if x > 0 { "positive" } else { "non-positive" };
    
  • Loops:

    • loop: Infinite loop (use break to exit).
    • while: Conditional loop.
    • for: Iterate over iterators.
    // Loop with break/continue
    let mut counter = 0;
    loop {
        counter += 1;
        if counter == 10 {
            break;
        }
    }
    
    // For loop
    for i in 0..5 {
        println!("{}", i);  // 0, 1, 2, 3, 4
    }
    
    // Iterate over vector
    let vec = vec![1, 2, 3];
    for val in vec.iter() {
        println!("{}", val);
    }
    
  • Match: Pattern matching (exhaustive).

    let number = 5;
    match number {
        1 => println!("One"),
        2 | 3 => println!("Two or Three"),
        4..=10 => println!("Between 4 and 10"),
        _ => println!("Something else"),
    }
    

Functions

Define with fn:

fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

fn main() {
    println!("{}", greet("World"));  // Output: Hello, World!
}
  • Parameters: fn add(a: i32, b: i32) -> i32 { a + b }
  • Return type: Explicit with -> or implicit last expression.
  • Closures: let square = |x| x * x;

Ownership and Borrowing

Rust's ownership system is its most distinctive feature—a set of rules checked at compile time that ensures memory safety without garbage collection. Understanding ownership is essential for writing idiomatic Rust code. This system eliminates entire categories of bugs: use-after-free, double-free, dangling pointers, and data races.

Memory Model: Stack vs Heap

Before diving into ownership, understanding Rust's memory model is crucial:

Stack:

  • Fixed-size data with known lifetime
  • LIFO (Last In, First Out) allocation/deallocation
  • Extremely fast (just moving a pointer)
  • Examples: integers, floats, booleans, fixed-size arrays, tuples of stack types

Heap:

  • Dynamically-sized data or data that needs to outlive its scope
  • Requires explicit allocation and deallocation
  • Slower than stack (finding space, bookkeeping)
  • Examples: String, Vec<T>, Box<T>, HashMap<K, V>
fn main() {
    // Stack allocation: size known at compile time
    let x: i32 = 42;           // 4 bytes on stack
    let arr: [i32; 3] = [1, 2, 3];  // 12 bytes on stack

    // Heap allocation: size can vary at runtime
    let s: String = String::from("hello");  // Pointer on stack, data on heap
    let v: Vec<i32> = vec![1, 2, 3, 4, 5];  // Pointer on stack, data on heap

    // String layout in memory:
    // Stack: | ptr | len: 5 | capacity: 5 |  (24 bytes on 64-bit)
    // Heap:  | h | e | l | l | o |
}

The Three Ownership Rules

  1. Each value in Rust has exactly one owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value is dropped (memory freed).

These rules are enforced at compile time—violations result in compiler errors, not runtime crashes.

fn main() {
    {
        let s = String::from("hello");  // s comes into scope, owns the String
        // s is valid here
    }  // s goes out of scope, `drop` is called, memory is freed

    // println!("{}", s);  // Error: s is not in scope
}

Move Semantics

When you assign a heap-allocated value to another variable, Rust moves ownership rather than copying:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // Ownership moves from s1 to s2

    // println!("{}", s1);  // Error: value borrowed here after move
    println!("{}", s2);    // OK: s2 now owns the String
}

Why move instead of copy? If both s1 and s2 pointed to the same heap data, when they go out of scope, Rust would try to free the same memory twice (double-free bug). Moving ensures only one owner exists.

Move in function calls:

fn takes_ownership(s: String) {
    println!("{}", s);
}  // s is dropped here

fn main() {
    let s = String::from("hello");
    takes_ownership(s);  // s is moved into the function
    // println!("{}", s);  // Error: s was moved
}

Returning ownership:

fn gives_ownership() -> String {
    let s = String::from("hello");
    s  // Ownership moves to the caller
}

fn takes_and_gives_back(s: String) -> String {
    s  // Ownership moves back to the caller
}

fn main() {
    let s1 = gives_ownership();           // s1 owns the returned String
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);    // s2 moved in, s3 owns the returned value
    // s2 is no longer valid; s1 and s3 are
}

Copy vs Clone

Copy Trait: Types that implement Copy are duplicated bit-for-bit on assignment (stack-only data):

fn main() {
    let x = 5;
    let y = x;  // Copy, not move
    println!("x = {}, y = {}", x, y);  // Both valid

    // Types that implement Copy:
    // - All integer types (i32, u64, etc.)
    // - Boolean (bool)
    // - Floating-point types (f32, f64)
    // - Character type (char)
    // - Tuples containing only Copy types: (i32, i32) is Copy, (i32, String) is not
    // - Arrays of Copy types with known size: [i32; 5]
}

Clone Trait: For explicit deep copies of heap data:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // Explicit deep copy

    println!("s1 = {}, s2 = {}", s1, s2);  // Both valid

    // clone() can be expensive: it copies all heap data
    let v1 = vec![1, 2, 3, 4, 5];
    let v2 = v1.clone();  // Copies the entire vector
}

Implementing Copy and Clone:

#[derive(Copy, Clone, Debug)]
struct Point {
    x: i32,
    y: i32,
}

// Cannot derive Copy if any field doesn't implement Copy
#[derive(Clone, Debug)]
struct Person {
    name: String,  // String doesn't implement Copy
    age: u32,
}

fn main() {
    let p1 = Point { x: 5, y: 10 };
    let p2 = p1;  // Copy
    println!("{:?} {:?}", p1, p2);  // Both valid

    let person1 = Person { name: String::from("Alice"), age: 30 };
    let person2 = person1.clone();  // Must explicitly clone
    // let person3 = person1;  // Would move person1
}

Borrowing: References

Borrowing allows you to reference data without taking ownership. References are pointers that are guaranteed to be valid.

Immutable References (&T):

fn calculate_length(s: &String) -> usize {
    s.len()
}  // s goes out of scope, but since it doesn't own the String, nothing is dropped

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);  // Pass a reference

    println!("The length of '{}' is {}.", s1, len);  // s1 still valid
}

Mutable References (&mut T):

fn append_world(s: &mut String) {
    s.push_str(" world");
}

fn main() {
    let mut s = String::from("hello");
    append_world(&mut s);
    println!("{}", s);  // "hello world"
}

The Borrowing Rules

Rust enforces two critical rules at compile time:

  1. At any given time, you can have either:

    • One mutable reference, OR
    • Any number of immutable references
  2. References must always be valid (no dangling references).

These rules prevent data races, which occur when:

  • Two or more pointers access the same data at the same time
  • At least one pointer is writing
  • There's no synchronization
fn main() {
    let mut s = String::from("hello");

    // Multiple immutable references are OK
    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // r1 and r2 are no longer used after this point

    // Now we can take a mutable reference
    let r3 = &mut s;
    r3.push_str(" world");
    println!("{}", r3);

    // This would fail:
    // let r1 = &s;
    // let r2 = &mut s;  // Error: cannot borrow as mutable while immutable borrow exists
    // println!("{}, {}", r1, r2);
}

Non-Lexical Lifetimes (NLL): Rust 2018+ uses NLL: references are considered "live" until their last use, not until the end of scope:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // r1 and r2 are no longer used after this point (NLL)

    let r3 = &mut s;  // OK because r1 and r2 are no longer used
    println!("{}", r3);
}

Dangling References

Rust prevents dangling references at compile time:

// This would not compile:
fn dangle() -> &String {
    let s = String::from("hello");
    &s  // Error: returns a reference to data owned by the current function
}  // s is dropped here, reference would be dangling

// Correct: return the owned value
fn no_dangle() -> String {
    let s = String::from("hello");
    s  // Ownership is moved out
}

Slices: References to Contiguous Sequences

Slices are references to a portion of a collection:

fn main() {
    let s = String::from("hello world");

    let hello: &str = &s[0..5];   // "hello"
    let world: &str = &s[6..11];  // "world"

    // Shorthand
    let hello = &s[..5];   // Same as &s[0..5]
    let world = &s[6..];   // Same as &s[6..len]
    let whole = &s[..];    // Same as &s[0..len]

    println!("{} {}", hello, world);

    // String literals are slices
    let literal: &str = "hello world";  // &str, stored in binary

    // Array slices
    let a = [1, 2, 3, 4, 5];
    let slice: &[i32] = &a[1..3];  // [2, 3]
}

Slices prevent bugs:

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);  // Immutable borrow

    // s.clear();  // Error: cannot borrow as mutable while immutable borrow exists
    println!("The first word is: {}", word);

    s.clear();  // OK: word is no longer used
}

Lifetimes

Lifetimes are Rust's way of ensuring references are valid for as long as they're used. Every reference has a lifetime—the scope for which the reference is valid.

Why lifetimes are needed:

// Which reference does the return value come from?
fn longest(x: &str, y: &str) -> &str {  // Error: missing lifetime specifier
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Lifetime Annotation Syntax:

&i32        // A reference
&'a i32     // A reference with an explicit lifetime 'a
&'a mut i32 // A mutable reference with an explicit lifetime 'a

Function Lifetimes:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("long string is long");

    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("The longest string is {}", result);
    }  // string2 goes out of scope, result would be invalid if it referenced string2
}

Understanding 'a: The lifetime 'a means "the returned reference will be valid for the shorter of the lifetimes of x and y."

fn main() {
    let string1 = String::from("long string");
    let result;

    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
        // result is valid here
    }  // string2 dropped

    // println!("{}", result);  // Error: string2 does not live long enough
}

Struct Lifetimes:

When a struct holds references, it needs lifetime annotations:

struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }

    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");

    let excerpt = ImportantExcerpt {
        part: first_sentence,
    };

    println!("{}", excerpt.part);
}  // excerpt cannot outlive novel

Lifetime Elision Rules:

The compiler can infer lifetimes in many cases using three rules:

  1. Each parameter that is a reference gets its own lifetime parameter.
  2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
  3. If there are multiple input lifetime parameters, but one is &self or &mut self, the lifetime of self is assigned to all output lifetime parameters.
// These are equivalent:
fn first_word(s: &str) -> &str { ... }
fn first_word<'a>(s: &'a str) -> &'a str { ... }

// Rule 3 in action:
impl<'a> ImportantExcerpt<'a> {
    // Elided: fn announce(&self, announcement: &str) -> &str
    // Expanded: fn announce<'b>(&'a self, announcement: &'b str) -> &'a str
    fn announce(&self, announcement: &str) -> &str {
        println!("{}", announcement);
        self.part
    }
}

Static Lifetime:

The 'static lifetime means the reference lives for the entire program:

// String literals have 'static lifetime
let s: &'static str = "I have a static lifetime.";

// Use sparingly: usually indicates global data or leaked memory

Lifetime Bounds:

Combine lifetimes with generics:

use std::fmt::Display;

fn longest_with_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Advanced Patterns

Multiple Lifetime Parameters:

fn complex<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    x  // Return only depends on 'a
}

// When lifetimes are related:
fn complex2<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
    if x.len() > 0 { x } else { y }  // 'b outlives 'a, so y is valid for 'a
}

Higher-Ranked Trait Bounds (HRTB):

fn apply_to_str<F>(f: F) -> String
where
    F: for<'a> Fn(&'a str) -> &'a str,  // F works for any lifetime
{
    let s = String::from("hello");
    f(&s).to_string()
}

Common Ownership Patterns

1. Taking and Returning Ownership:

fn process(mut data: Vec<i32>) -> Vec<i32> {
    data.push(42);
    data  // Return ownership
}

2. Borrowing When Possible:

fn analyze(data: &[i32]) -> i32 {
    data.iter().sum()  // Just needs to read
}

3. Mutable Borrow for In-Place Modification:

fn double_all(data: &mut [i32]) {
    for item in data.iter_mut() {
        *item *= 2;
    }
}

4. Clone When Necessary:

fn use_both(original: &str) -> (String, String) {
    let copy = original.to_string();
    let processed = copy.to_uppercase();
    (copy, processed)  // Need two owned values
}

5. Interior Mutability (RefCell, Cell):

When you need mutable access through an immutable reference:

use std::cell::RefCell;

struct Cache {
    data: RefCell<Option<String>>,
}

impl Cache {
    fn get(&self) -> String {  // Note: &self, not &mut self
        let mut cache = self.data.borrow_mut();
        if cache.is_none() {
            *cache = Some(String::from("computed value"));
        }
        cache.as_ref().unwrap().clone()
    }
}

Best Practices

  1. Prefer borrowing over ownership transfer when you don't need to own the data.
  2. Use &str instead of &String for function parameters (more flexible).
  3. Avoid excessive cloning—it's often a sign of fighting the borrow checker.
  4. Let the compiler guide you—error messages explain what's wrong.
  5. Use slices for viewing portions of collections.
  6. Lifetimes are about ensuring safety, not controlling memory allocation.
  7. When in doubt, own the data—then refactor to borrowing if needed.

Bit Manipulation

Rust provides comprehensive bit manipulation operators and methods.

Basic Bit Operators

  • & (AND): Returns 1 if both bits are 1
  • | (OR): Returns 1 if at least one bit is 1
  • ^ (XOR): Returns 1 if exactly one bit is 1
  • ! (NOT): Inverts all bits
  • \<< (Left shift): Shifts bits to the left
  • >> (Right shift): Shifts bits to the right

Examples

// Bitwise AND
let a: u8 = 60;  // 0011 1100
let b: u8 = 13;  // 0000 1101
println!("{}", a & b);  // 12 (0000 1100)

// Bitwise OR
println!("{}", a | b);  // 61 (0011 1101)

// Bitwise XOR
println!("{}", a ^ b);  // 49 (0011 0001)

// Bitwise NOT
println!("{}", !a);  // 195 (1100 0011)

// Left Shift
println!("{}", a << 2);  // 240 (1111 0000)

// Right Shift
println!("{}", a >> 2);  // 15 (0000 1111)

Common Bit Manipulation Techniques

1. Check if a number is even or odd

fn is_even(num: i32) -> bool {
    (num & 1) == 0
}

println!("{}", is_even(42));  // true
println!("{}", is_even(7));   // false

2. Check if the ith bit is set

fn is_bit_set(num: u32, i: u32) -> bool {
    (num & (1 << i)) != 0
}

println!("{}", is_bit_set(10, 1));  // true (10 is 1010 in binary, bit 1 is set)
println!("{}", is_bit_set(10, 0));  // false (bit 0 is not set)

3. Set the ith bit

fn set_bit(num: u32, i: u32) -> u32 {
    num | (1 << i)
}

println!("{}", set_bit(10, 0));  // 11 (changes 1010 to 1011)

4. Clear the ith bit

fn clear_bit(num: u32, i: u32) -> u32 {
    num & !(1 << i)
}

println!("{}", clear_bit(10, 1));  // 8 (changes 1010 to 1000)

5. Toggle the ith bit

fn toggle_bit(num: u32, i: u32) -> u32 {
    num ^ (1 << i)
}

println!("{}", toggle_bit(10, 0));  // 11 (changes 1010 to 1011)
println!("{}", toggle_bit(10, 1));  // 8 (changes 1010 to 1000)

6. Count set bits (Hamming weight)

fn count_set_bits(mut num: u32) -> u32 {
    let mut count = 0;
    while num != 0 {
        count += num & 1;
        num >>= 1;
    }
    count
}

// Using built-in method
fn count_set_bits_builtin(num: u32) -> u32 {
    num.count_ones()
}

println!("{}", count_set_bits(10));  // 2 (1010 has two 1s)

Practical Applications

1. Bit Masking

// Using bit masks to store multiple boolean flags in a single integer
const READ: u8 = 1;     // 001
const WRITE: u8 = 2;    // 010
const EXECUTE: u8 = 4;  // 100

// Set permissions
let mut permissions: u8 = 0;
permissions |= READ;    // Add read permission
permissions |= WRITE;   // Add write permission

// Check permissions
let has_read = (permissions & READ) != 0;
let has_write = (permissions & WRITE) != 0;
let has_execute = (permissions & EXECUTE) != 0;

println!("Read: {}, Write: {}, Execute: {}", has_read, has_write, has_execute);
// Output: Read: true, Write: true, Execute: false

2. Power of Two

fn is_power_of_two(num: u32) -> bool {
    num > 0 && (num & (num - 1)) == 0
}

println!("{}", is_power_of_two(16));  // true
println!("{}", is_power_of_two(18));  // false

Advanced Techniques

1. Swapping variables without a temporary variable

fn swap_without_temp(mut a: i32, mut b: i32) -> (i32, i32) {
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    (a, b)
}

let (a, b) = swap_without_temp(5, 7);
println!("a = {}, b = {}", a, b);  // a = 7, b = 5

2. Find the single number in an array where all other numbers appear twice

fn find_single(nums: &[i32]) -> i32 {
    nums.iter().fold(0, |acc, &x| acc ^ x)
}

println!("{}", find_single(&[4, 1, 2, 1, 2]));  // 4

Bit manipulation is particularly useful in systems programming, cryptography, embedded systems, and performance-critical algorithms.

Mathematics and Geometry

Rust provides excellent support for mathematical operations through the standard library and ecosystem.

Core Mathematical Libraries

  • Standard Library: Basic math functions in std::f32 and std::f64 modules.
  • num: Comprehensive numeric traits and types (num-traits, num-complex).
  • nalgebra: Linear algebra library for vectors, matrices, and geometric transformations.
  • cgmath: Computer graphics math library.
  • rust_decimal: Decimal arithmetic for financial calculations.
// Basic arithmetic
let a = 5.0;
let b = 3.0;
println!("{}", a + b);  // 8.0

// Math functions
use std::f64::consts::PI;
let angle = PI / 4.0;
println!("sin(Ï€/4) = {}", angle.sin());
println!("cos(Ï€/4) = {}", angle.cos());

Geometry in Rust

1. Using nalgebra for Linear Algebra

// Add to Cargo.toml: nalgebra = "0.32"
use nalgebra::{Vector2, Matrix2, Point2};

// Calculate Euclidean distance between two points
let point1 = Point2::new(0.0, 0.0);
let point2 = Point2::new(3.0, 4.0);
let distance = (point2 - point1).norm();
println!("Distance: {}", distance);  // Distance: 5.0

// Matrix operations
let matrix_a = Matrix2::new(1.0, 2.0, 3.0, 4.0);
let matrix_b = Matrix2::new(5.0, 6.0, 7.0, 8.0);
let result = matrix_a * matrix_b;  // Matrix multiplication
println!("{}", result);

2. Custom Geometry Types

#[derive(Debug, Clone, Copy)]
struct Point {
    x: f64,
    y: f64,
}

impl Point {
    fn new(x: f64, y: f64) -> Self {
        Point { x, y }
    }

    fn distance_to(&self, other: &Point) -> f64 {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        (dx * dx + dy * dy).sqrt()
    }
}

#[derive(Debug)]
struct Triangle {
    p1: Point,
    p2: Point,
    p3: Point,
}

impl Triangle {
    fn area(&self) -> f64 {
        // Using cross product
        let v1 = Point::new(self.p2.x - self.p1.x, self.p2.y - self.p1.y);
        let v2 = Point::new(self.p3.x - self.p1.x, self.p3.y - self.p1.y);
        let cross = v1.x * v2.y - v1.y * v2.x;
        (cross.abs()) / 2.0
    }
}

let triangle = Triangle {
    p1: Point::new(0.0, 0.0),
    p2: Point::new(1.0, 0.0),
    p3: Point::new(0.0, 2.0),
};
println!("Triangle area: {}", triangle.area());  // Triangle area: 1.0

3. Geometric Transformations

use nalgebra::{Rotation2, Vector2};

// 2D rotation (45 degrees)
let angle = std::f64::consts::PI / 4.0;
let rotation = Rotation2::new(angle);

// Define a point
let point = Vector2::new(1.0, 0.0);

// Apply rotation
let rotated_point = rotation * point;
println!("Original point: ({}, {})", point.x, point.y);
println!("Rotated point: ({}, {})", rotated_point.x, rotated_point.y);

Advanced Geometric Calculations

Computational Geometry Algorithms

// Convex Hull using Graham scan (simplified)
fn convex_hull(points: &[Point]) -> Vec<Point> {
    if points.len() < 3 {
        return points.to_vec();
    }

    // Find bottom-most point (or leftmost in case of tie)
    let mut bottom = 0;
    for i in 1..points.len() {
        if points[i].y < points[bottom].y ||
           (points[i].y == points[bottom].y && points[i].x < points[bottom].x) {
            bottom = i;
        }
    }

    // Sort points by polar angle with respect to bottom point
    // Implementation details omitted for brevity
    points.to_vec()  // Simplified
}

Advanced Rust Concepts and Techniques

Rust offers powerful advanced features that enable safe systems programming, zero-cost abstractions, and fearless concurrency. These features form the foundation for writing high-performance, correct, and maintainable Rust code. Understanding them deeply is essential for mastering Rust and leveraging its full potential.

  1. Traits - Define shared behavior and enable polymorphism
  2. Generics - Write code that works with multiple types
  3. Error Handling - Robust error management without exceptions
  4. Smart Pointers - Safe memory management abstractions
  5. Concurrency - Fearless parallelism with compile-time guarantees
  6. Macros - Metaprogramming and code generation
  7. Unsafe Rust - Low-level control when needed
  8. Async/Await - Efficient asynchronous programming
  9. Iterators - Lazy, composable sequence processing
  10. Closures - Anonymous functions with environment capture

1. Traits

Traits are Rust's primary mechanism for defining shared behavior across types. They serve multiple roles: defining interfaces (like Java/C# interfaces), enabling polymorphism (both static and dynamic), implementing operator overloading, and providing extension methods. Unlike inheritance-based OOP, traits promote composition and are central to Rust's "zero-cost abstraction" philosophy—trait method calls are typically resolved at compile time with no runtime overhead.

Key Concepts:

  • Trait Definition: Declares method signatures (and optionally default implementations) that implementing types must provide
  • Trait Implementation: Types implement traits using impl Trait for Type syntax
  • Trait Bounds: Constrain generic types to those implementing specific traits
  • Trait Objects: Enable dynamic dispatch via dyn Trait for runtime polymorphism
  • Associated Types: Type placeholders defined within traits
  • Blanket Implementations: Implement traits for all types matching certain bounds
  • The Orphan Rule: Either the trait or the type must be local to your crate

Basic Trait Definition and Implementation

Traits define a set of methods that a type must implement. The implementing type provides concrete behavior:

// Define a trait with a required method
trait Drawable {
    fn draw(&self);

    // Optional: method with default implementation
    fn description(&self) -> String {
        String::from("A drawable shape")
    }
}

struct Circle {
    radius: f64,
    center: (f64, f64),
}

struct Rectangle {
    width: f64,
    height: f64,
    position: (f64, f64),
}

// Implement the trait for Circle
impl Drawable for Circle {
    fn draw(&self) {
        println!(
            "Drawing circle at ({}, {}) with radius {}",
            self.center.0, self.center.1, self.radius
        );
    }

    // Override the default implementation
    fn description(&self) -> String {
        format!("Circle with radius {:.2}", self.radius)
    }
}

// Implement the trait for Rectangle
impl Drawable for Rectangle {
    fn draw(&self) {
        println!(
            "Drawing rectangle at ({}, {}) - {}x{}",
            self.position.0, self.position.1, self.width, self.height
        );
    }
    // Uses default description() implementation
}

fn main() {
    let circle = Circle { radius: 5.0, center: (0.0, 0.0) };
    let rect = Rectangle { width: 10.0, height: 20.0, position: (5.0, 5.0) };

    circle.draw();
    println!("Description: {}", circle.description());

    rect.draw();
    println!("Description: {}", rect.description()); // Uses default
}

Default Implementations and Method Dependencies

Traits can provide default implementations that call other trait methods. This creates powerful composition patterns:

trait Summary {
    // Required method - must be implemented
    fn summarize_author(&self) -> String;

    // Default implementation calling the required method
    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }

    // Another default method
    fn preview(&self) -> String {
        let summary = self.summarize();
        if summary.len() > 50 {
            format!("{}...", &summary[..47])
        } else {
            summary
        }
    }
}

struct Article {
    title: String,
    author: String,
    content: String,
}

struct Tweet {
    username: String,
    content: String,
    retweets: u32,
}

impl Summary for Article {
    fn summarize_author(&self) -> String {
        self.author.clone()
    }

    // Override the default summarize
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
    // Uses default summarize() and preview()
}

Trait Bounds and Generic Constraints

Trait bounds constrain generic types to only those implementing specific traits. This enables writing generic code that relies on specific behavior:

use std::fmt::{Display, Debug};

// Simple trait bound using : syntax
fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

// Multiple bounds with + syntax
fn notify_and_display<T: Summary + Display>(item: &T) {
    println!("Item: {}", item);
    println!("Summary: {}", item.summarize());
}

// Complex bounds are clearer with where clause
fn complex_function<T, U>(t: &T, u: &U) -> String
where
    T: Display + Clone + Summary,
    U: Clone + Debug + Default,
{
    format!("t: {}, u: {:?}", t, u)
}

// Trait bounds on impl blocks - conditional implementation
struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

// Only implement cmp_display for Pairs where T implements both traits
impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

// impl Trait syntax - simpler for function parameters and returns
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("rustlang"),
        content: String::from("Hello, Rustaceans!"),
        retweets: 100,
    }
}

Associated Types vs Generic Traits

Associated types define placeholder types within traits. They differ from generic traits in important ways:

// With associated types: one implementation per type
trait Iterator {
    type Item;  // Associated type - implementor chooses once

    fn next(&mut self) -> Option<Self::Item>;
}

// With generics: multiple implementations possible per type
trait ConvertTo<T> {
    fn convert(&self) -> T;
}

// Example: Counter can only have ONE Iterator implementation
struct Counter {
    count: u32,
    max: u32,
}

impl Iterator for Counter {
    type Item = u32;  // Fixed for Counter

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < self.max {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

// Example: A type can implement ConvertTo multiple times
struct Temperature(f64);

impl ConvertTo<f64> for Temperature {
    fn convert(&self) -> f64 { self.0 }
}

impl ConvertTo<i32> for Temperature {
    fn convert(&self) -> i32 { self.0 as i32 }
}

impl ConvertTo<String> for Temperature {
    fn convert(&self) -> String { format!("{}°C", self.0) }
}

// Associated types with bounds
trait Container {
    type Item: Clone + Debug;  // Item must implement Clone and Debug

    fn get(&self, index: usize) -> Option<&Self::Item>;
    fn len(&self) -> usize;
}

Trait Objects and Dynamic Dispatch

Trait objects enable runtime polymorphism through dynamic dispatch. They use a vtable (virtual method table) to resolve method calls at runtime:

trait Drawable {
    fn draw(&self);
    fn bounding_box(&self) -> (f64, f64, f64, f64);  // (x, y, width, height)
}

struct Circle { x: f64, y: f64, radius: f64 }
struct Rectangle { x: f64, y: f64, width: f64, height: f64 }
struct Triangle { points: [(f64, f64); 3] }

impl Drawable for Circle {
    fn draw(&self) { println!("Drawing circle at ({}, {})", self.x, self.y); }
    fn bounding_box(&self) -> (f64, f64, f64, f64) {
        (self.x - self.radius, self.y - self.radius,
         self.radius * 2.0, self.radius * 2.0)
    }
}

impl Drawable for Rectangle {
    fn draw(&self) { println!("Drawing rectangle at ({}, {})", self.x, self.y); }
    fn bounding_box(&self) -> (f64, f64, f64, f64) {
        (self.x, self.y, self.width, self.height)
    }
}

impl Drawable for Triangle {
    fn draw(&self) { println!("Drawing triangle"); }
    fn bounding_box(&self) -> (f64, f64, f64, f64) {
        let (min_x, max_x) = self.points.iter()
            .fold((f64::MAX, f64::MIN), |(min, max), p| (min.min(p.0), max.max(p.0)));
        let (min_y, max_y) = self.points.iter()
            .fold((f64::MAX, f64::MIN), |(min, max), p| (min.min(p.1), max.max(p.1)));
        (min_x, min_y, max_x - min_x, max_y - min_y)
    }
}

// Using trait objects for heterogeneous collections
fn draw_all(shapes: &[Box<dyn Drawable>]) {
    for shape in shapes {
        shape.draw();  // Dynamic dispatch via vtable
    }
}

// Trait objects with references
fn draw_shape(shape: &dyn Drawable) {
    shape.draw();
}

fn main() {
    // Heterogeneous collection of shapes
    let shapes: Vec<Box<dyn Drawable>> = vec![
        Box::new(Circle { x: 0.0, y: 0.0, radius: 5.0 }),
        Box::new(Rectangle { x: 10.0, y: 10.0, width: 20.0, height: 15.0 }),
        Box::new(Triangle { points: [(0.0, 0.0), (5.0, 10.0), (10.0, 0.0)] }),
    ];

    draw_all(&shapes);

    // Calculate total bounding area
    let total_area: f64 = shapes.iter()
        .map(|s| {
            let (_, _, w, h) = s.bounding_box();
            w * h
        })
        .sum();
    println!("Total bounding area: {}", total_area);
}

Object Safety: Not all traits can be made into trait objects. A trait is object-safe if:

  • All methods have self as a receiver (not Self type in parameters/return)
  • No generic type parameters on methods
  • No associated functions (methods without self)
// Object-safe trait
trait ObjectSafe {
    fn method(&self);
    fn method_with_param(&self, x: i32);
}

// NOT object-safe (cannot use as dyn ObjectUnsafe)
trait ObjectUnsafe {
    fn returns_self(&self) -> Self;  // Returns Self
    fn generic_method<T>(&self, x: T);  // Generic method
    fn static_method();  // No self parameter
}

Supertraits and Trait Inheritance

Traits can require other traits as prerequisites (supertraits):

use std::fmt::Display;

// OutlinePrint requires Display as a supertrait
trait OutlinePrint: Display {
    fn outline_print(&self) {
        let output = self.to_string();  // Can use Display methods
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

// Multiple supertraits
trait Drawable: Clone + Debug {
    fn draw(&self);
}

struct Point {
    x: i32,
    y: i32,
}

impl Display for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl OutlinePrint for Point {}  // Must also implement Display

Blanket Implementations

Implement traits for all types matching certain bounds:

use std::fmt::Display;

// Blanket implementation: ToString for all Display types
impl<T: Display> ToString for T {
    fn to_string(&self) -> String {
        format!("{}", self)
    }
}

// Custom blanket implementation example
trait Printable {
    fn print(&self);
}

// Implement Printable for ALL types that implement Debug
impl<T: std::fmt::Debug> Printable for T {
    fn print(&self) {
        println!("{:?}", self);
    }
}

// Now any Debug type automatically has print()
fn demo() {
    let vec = vec![1, 2, 3];
    vec.print();  // Works because Vec<i32> implements Debug

    "hello".print();  // Works for &str
    42.print();  // Works for i32
}

Operator Overloading with Traits

Rust uses traits from std::ops for operator overloading:

use std::ops::{Add, Sub, Mul, Neg, Index};

#[derive(Debug, Clone, Copy, PartialEq)]
struct Vector2D {
    x: f64,
    y: f64,
}

impl Vector2D {
    fn new(x: f64, y: f64) -> Self {
        Vector2D { x, y }
    }

    fn magnitude(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }
}

// Implement + operator
impl Add for Vector2D {
    type Output = Vector2D;

    fn add(self, other: Vector2D) -> Vector2D {
        Vector2D {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

// Implement - operator
impl Sub for Vector2D {
    type Output = Vector2D;

    fn sub(self, other: Vector2D) -> Vector2D {
        Vector2D {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}

// Implement * for scalar multiplication
impl Mul<f64> for Vector2D {
    type Output = Vector2D;

    fn mul(self, scalar: f64) -> Vector2D {
        Vector2D {
            x: self.x * scalar,
            y: self.y * scalar,
        }
    }
}

// Implement unary - (negation)
impl Neg for Vector2D {
    type Output = Vector2D;

    fn neg(self) -> Vector2D {
        Vector2D {
            x: -self.x,
            y: -self.y,
        }
    }
}

fn main() {
    let v1 = Vector2D::new(3.0, 4.0);
    let v2 = Vector2D::new(1.0, 2.0);

    let sum = v1 + v2;
    let diff = v1 - v2;
    let scaled = v1 * 2.0;
    let negated = -v1;

    println!("v1 + v2 = {:?}", sum);
    println!("v1 - v2 = {:?}", diff);
    println!("v1 * 2 = {:?}", scaled);
    println!("-v1 = {:?}", negated);
    println!("|v1| = {}", v1.magnitude());
}

Marker Traits and Auto Traits

Marker traits have no methods but indicate type properties:

// Standard marker traits (auto-implemented by the compiler)
// Send: Safe to transfer ownership to another thread
// Sync: Safe to share references between threads

use std::marker::PhantomData;

// Creating a custom marker trait
trait Immutable {}  // Marker: type should not be mutated

// PhantomData for unused type parameters
struct Container<T> {
    data: Vec<u8>,
    _marker: PhantomData<T>,  // Indicates T is logically owned
}

impl<T> Container<T> {
    fn new() -> Self {
        Container {
            data: Vec::new(),
            _marker: PhantomData,
        }
    }
}

// Negative trait implementations (unstable, shown for understanding)
// impl !Send for MyType {}  // Explicitly opt-out of Send

The Orphan Rule and Newtype Pattern

The orphan rule prevents implementing external traits on external types. The newtype pattern provides a workaround:

use std::fmt::{Display, Formatter, Result};

// Can't implement Display for Vec<String> directly (both external)
// Solution: Newtype pattern

struct Wrapper(Vec<String>);

impl Display for Wrapper {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

// Deref to use Vec methods transparently
impl std::ops::Deref for Wrapper {
    type Target = Vec<String>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn main() {
    let w = Wrapper(vec!["hello".to_string(), "world".to_string()]);
    println!("{}", w);  // Uses our Display implementation
    println!("Length: {}", w.len());  // Uses Vec's len() via Deref
}

Extension Traits Pattern

Add methods to existing types by defining a new trait:

// Extension trait for &str
trait StringExt {
    fn is_blank(&self) -> bool;
    fn truncate_with_ellipsis(&self, max_len: usize) -> String;
}

impl StringExt for str {
    fn is_blank(&self) -> bool {
        self.trim().is_empty()
    }

    fn truncate_with_ellipsis(&self, max_len: usize) -> String {
        if self.len() <= max_len {
            self.to_string()
        } else if max_len <= 3 {
            "...".to_string()
        } else {
            format!("{}...", &self[..max_len - 3])
        }
    }
}

fn main() {
    let text = "   ";
    println!("Is blank: {}", text.is_blank());  // true

    let long_text = "This is a very long string that needs truncation";
    println!("{}", long_text.truncate_with_ellipsis(20));  // "This is a very lo..."
}

2. Generics

Generics enable writing code that works with multiple types while maintaining full type safety. Rust implements generics through monomorphization—the compiler generates specialized code for each concrete type used, resulting in zero runtime overhead. This "pay for what you use" approach means generic code runs exactly as fast as hand-written type-specific code.

Key Concepts:

  • Type Parameters: Placeholders like <T> that represent any type
  • Lifetime Parameters: Generic lifetimes like <'a> for reference validity
  • Const Generics: Compile-time constant values as parameters
  • Monomorphization: Compiler generates specialized code for each concrete type
  • Turbofish Syntax: Explicit type specification with ::<Type>

Basic Generic Functions

Generic functions work with any type that satisfies their trait bounds:

// Generic function with trait bounds
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

// Without Copy bound - returns reference
fn largest_ref<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

// Multiple type parameters
fn combine<T, U>(first: T, second: U) -> (T, U) {
    (first, second)
}

// Generic with complex bounds
fn debug_and_clone<T>(item: &T) -> T
where
    T: std::fmt::Debug + Clone,
{
    println!("Debug: {:?}", item);
    item.clone()
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    let result = largest(&numbers);
    println!("Largest number: {}", result);

    let chars = vec!['y', 'm', 'a', 'q'];
    let result = largest(&chars);
    println!("Largest char: {}", result);

    // Turbofish syntax for explicit type annotation
    let parsed = "42".parse::<i32>().unwrap();
    let collected: Vec<i32> = (0..10).collect();
    let collected_turbofish = (0..10).collect::<Vec<i32>>();
}

Generic Structs, Enums, and Methods

// Generic struct with single type parameter
struct Point<T> {
    x: T,
    y: T,
}

// Generic struct with multiple type parameters
struct KeyValue<K, V> {
    key: K,
    value: V,
}

// Methods on generic structs
impl<T> Point<T> {
    fn new(x: T, y: T) -> Self {
        Point { x, y }
    }

    fn x(&self) -> &T {
        &self.x
    }

    fn y(&self) -> &T {
        &self.y
    }
}

// Method with additional type parameters
impl<T> Point<T> {
    fn mixup<U>(self, other: Point<U>) -> Point<T> {
        Point {
            x: self.x,
            y: other.y,  // This won't compile - types differ
        }
    }
}

// Correct version with mixed result
impl<T, U> Point<T> {
    fn mixup_correct<V, W>(self, other: Point<V>) -> (T, V) {
        (self.x, other.x)
    }
}

// Specialized implementation for specific types
impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }

    fn distance_to(&self, other: &Point<f64>) -> f64 {
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }
}

// Conditional trait implementation
impl<T: std::fmt::Display> Point<T> {
    fn display(&self) {
        println!("Point({}, {})", self.x, self.y);
    }
}

// Standard library generic enums
enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

// Custom generic enum
enum BinaryTree<T> {
    Empty,
    Node {
        value: T,
        left: Box<BinaryTree<T>>,
        right: Box<BinaryTree<T>>,
    },
}

impl<T: Ord> BinaryTree<T> {
    fn new() -> Self {
        BinaryTree::Empty
    }

    fn insert(&mut self, value: T) {
        match self {
            BinaryTree::Empty => {
                *self = BinaryTree::Node {
                    value,
                    left: Box::new(BinaryTree::Empty),
                    right: Box::new(BinaryTree::Empty),
                };
            }
            BinaryTree::Node { value: ref node_value, left, right } => {
                if value < *node_value {
                    left.insert(value);
                } else {
                    right.insert(value);
                }
            }
        }
    }
}

Const Generics

Const generics allow using compile-time constant values as type parameters. This is powerful for array sizes, buffer capacities, and other compile-time configurations:

// Array wrapper with const generic size
#[derive(Debug)]
struct Array<T, const N: usize> {
    data: [T; N],
}

impl<T: Default + Copy, const N: usize> Array<T, N> {
    fn new() -> Self {
        Array {
            data: [T::default(); N],
        }
    }

    fn len(&self) -> usize {
        N  // Compile-time constant
    }

    fn is_empty(&self) -> bool {
        N == 0
    }
}

impl<T, const N: usize> Array<T, N> {
    fn from_array(data: [T; N]) -> Self {
        Array { data }
    }
}

// Const generic with bounds
struct FixedBuffer<const SIZE: usize>
where
    [(); SIZE]: Sized,  // Ensure SIZE creates valid array
{
    data: [u8; SIZE],
    len: usize,
}

impl<const SIZE: usize> FixedBuffer<SIZE> {
    fn new() -> Self {
        FixedBuffer {
            data: [0; SIZE],
            len: 0,
        }
    }

    fn push(&mut self, byte: u8) -> Result<(), &'static str> {
        if self.len >= SIZE {
            Err("Buffer full")
        } else {
            self.data[self.len] = byte;
            self.len += 1;
            Ok(())
        }
    }
}

// Const generics in functions
fn split_at_middle<T, const N: usize>(arr: [T; N]) -> ([T; N/2], [T; N/2])
where
    T: Default + Copy,
{
    let mut left = [T::default(); N/2];
    let mut right = [T::default(); N/2];

    for (i, item) in arr.into_iter().enumerate() {
        if i < N/2 {
            left[i] = item;
        } else if i < N {
            right[i - N/2] = item;
        }
    }
    (left, right)
}

fn main() {
    let arr: Array<i32, 5> = Array::new();
    println!("Length: {}", arr.len());

    let mut buffer: FixedBuffer<1024> = FixedBuffer::new();
    buffer.push(42).unwrap();

    let data = [1, 2, 3, 4, 5, 6];
    let (left, right) = split_at_middle(data);
    println!("Left: {:?}, Right: {:?}", left, right);
}

Lifetime Generics

Lifetimes are a form of generics that ensure references remain valid:

// Lifetime parameter ensures returned reference is valid
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Multiple lifetime parameters
fn first_word<'a, 'b>(s: &'a str, _marker: &'b str) -> &'a str {
    s.split_whitespace().next().unwrap_or("")
}

// Struct with lifetime parameter
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    fn level(&self) -> i32 {
        3
    }

    // Method returning reference with same lifetime
    fn announce_and_return_part(&self, announcement: &str) -> &'a str {
        println!("Attention: {}", announcement);
        self.part
    }
}

// Combining lifetimes with generic types
struct ImportantExcerpt<'a, T> {
    part: &'a str,
    metadata: T,
}

impl<'a, T: std::fmt::Debug> ImportantExcerpt<'a, T> {
    fn display(&self) {
        println!("Part: {}, Metadata: {:?}", self.part, self.metadata);
    }
}

// Static lifetime - lives for entire program duration
fn static_string() -> &'static str {
    "I live forever!"
}

// Lifetime bounds on generic types
fn longest_with_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: std::fmt::Display,
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() { x } else { y }
}

PhantomData and Variance

PhantomData indicates ownership of a type parameter without storing it:

use std::marker::PhantomData;

// PhantomData indicates T is logically owned even if not stored
struct Identifier<T> {
    id: u64,
    _marker: PhantomData<T>,  // Zero-sized, indicates type association
}

struct User;
struct Product;

impl<T> Identifier<T> {
    fn new(id: u64) -> Self {
        Identifier { id, _marker: PhantomData }
    }
}

fn process_user(id: Identifier<User>) {
    println!("Processing user {}", id.id);
}

fn main() {
    let user_id: Identifier<User> = Identifier::new(42);
    let product_id: Identifier<Product> = Identifier::new(42);

    process_user(user_id);  // OK
    // process_user(product_id);  // Error: expected Identifier<User>
}

// PhantomData affecting variance
struct Invariant<'a, T> {
    data: *mut T,
    _marker: PhantomData<&'a mut T>,  // Makes T invariant
}

// Covariant example
struct Covariant<'a, T> {
    data: &'a T,  // Covariant in 'a and T
}

Generic Associated Types (GATs)

GATs allow associated types in traits to be generic (stabilized in Rust 1.65):

// GAT example: Lending iterator pattern
trait LendingIterator {
    type Item<'a> where Self: 'a;  // GAT: Item is generic over lifetime

    fn next(&mut self) -> Option<Self::Item<'_>>;
}

// Implementation example
struct WindowsMut<'a, T> {
    slice: &'a mut [T],
    start: usize,
    window_size: usize,
}

impl<'a, T> LendingIterator for WindowsMut<'a, T> {
    type Item<'b> = &'b mut [T] where Self: 'b;

    fn next(&mut self) -> Option<Self::Item<'_>> {
        if self.start + self.window_size > self.slice.len() {
            return None;
        }
        let window = &mut self.slice[self.start..self.start + self.window_size];
        self.start += 1;
        // This requires unsafe in practice due to reborrowing constraints
        Some(unsafe { &mut *(window as *mut [T]) })
    }
}

// Simpler GAT example: Collection trait
trait Collection {
    type Item;
    type Iter<'a>: Iterator<Item = &'a Self::Item> where Self: 'a;

    fn iter(&self) -> Self::Iter<'_>;
}

impl<T> Collection for Vec<T> {
    type Item = T;
    type Iter<'a> = std::slice::Iter<'a, T> where T: 'a;

    fn iter(&self) -> Self::Iter<'_> {
        self.as_slice().iter()
    }
}

3. Error Handling

Rust takes a unique approach to error handling that avoids exceptions entirely. Instead, it uses the type system to encode error possibilities, making error handling explicit and enforced by the compiler. This leads to more robust code where errors cannot be accidentally ignored.

Two Categories of Errors:

  • Recoverable errors (Result<T, E>): Expected failures that can be handled gracefully
  • Unrecoverable errors (panic!): Bugs or invariant violations where the program cannot continue

Key Concepts:

  • Result Type: Encodes success (Ok) or failure (Err) in the type system
  • Option Type: Encodes presence (Some) or absence (None) of a value
  • ? Operator: Ergonomic error propagation that returns early on error
  • Custom Error Types: Application-specific errors with context
  • Error Conversion: From trait for automatic error type conversion
  • Error Crates: thiserror for libraries, anyhow for applications

The Result Type

Result<T, E> is Rust's primary error handling type:

use std::fs::File;
use std::io::{self, Read, Write};

// Basic Result usage
fn read_file_contents(filename: &str) -> Result<String, io::Error> {
    let mut file = File::open(filename)?;  // ? returns early on error
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

// Chaining with ? operator
fn read_first_line(filename: &str) -> Result<String, io::Error> {
    let contents = read_file_contents(filename)?;
    let first_line = contents
        .lines()
        .next()
        .unwrap_or("")
        .to_string();
    Ok(first_line)
}

// Using combinators instead of ?
fn read_username_from_file(path: &str) -> Result<String, io::Error> {
    std::fs::read_to_string(path)  // Returns Result<String, io::Error>
        .map(|s| s.trim().to_string())
}

// Result methods for transformation
fn demonstrate_result_methods() {
    let ok: Result<i32, &str> = Ok(5);
    let err: Result<i32, &str> = Err("error");

    // map: Transform success value
    let doubled = ok.map(|x| x * 2);  // Ok(10)

    // map_err: Transform error value
    let new_err = err.map_err(|e| format!("Error: {}", e));

    // and_then: Chain operations that return Result
    let chained = ok.and_then(|x| {
        if x > 0 { Ok(x * 2) } else { Err("negative") }
    });

    // or_else: Provide fallback for errors
    let recovered = err.or_else(|_| Ok(0));

    // unwrap_or: Provide default value on error
    let with_default = err.unwrap_or(0);  // 0

    // unwrap_or_else: Compute default lazily
    let computed = err.unwrap_or_else(|e| {
        println!("Recovering from: {}", e);
        42
    });

    // ok(): Convert Result<T, E> to Option<T>
    let maybe: Option<i32> = ok.ok();  // Some(5)

    // err(): Convert Result<T, E> to Option<E>
    let maybe_err: Option<&str> = err.err();  // Some("error")
}

fn main() {
    match read_file_contents("config.txt") {
        Ok(contents) => println!("Config: {}", contents),
        Err(e) => eprintln!("Failed to read config: {}", e),
    }

    // Using if let for simple cases
    if let Ok(contents) = read_file_contents("optional.txt") {
        println!("Optional file: {}", contents);
    }
}

The Option Type

Option<T> handles the absence of values without null:

fn find_user(id: u64) -> Option<String> {
    let users = vec![
        (1, "Alice"),
        (2, "Bob"),
        (3, "Charlie"),
    ];

    users.iter()
        .find(|(user_id, _)| *user_id == id)
        .map(|(_, name)| name.to_string())
}

fn demonstrate_option_methods() {
    let some_value: Option<i32> = Some(5);
    let none_value: Option<i32> = None;

    // map: Transform inner value
    let doubled = some_value.map(|x| x * 2);  // Some(10)

    // and_then (flatMap): Chain Option-returning functions
    let chained = some_value.and_then(|x| {
        if x > 0 { Some(x * 2) } else { None }
    });

    // or: Provide fallback Option
    let with_fallback = none_value.or(Some(0));  // Some(0)

    // or_else: Compute fallback lazily
    let computed = none_value.or_else(|| Some(42));

    // unwrap_or: Default value
    let value = none_value.unwrap_or(0);  // 0

    // filter: Keep Some only if predicate matches
    let filtered = some_value.filter(|x| *x > 10);  // None

    // ok_or: Convert Option<T> to Result<T, E>
    let result: Result<i32, &str> = some_value.ok_or("no value");

    // ok_or_else: Lazy error creation
    let result2: Result<i32, String> = none_value
        .ok_or_else(|| format!("Value not found"));

    // take: Take value out, leaving None
    let mut opt = Some(5);
    let taken = opt.take();  // Some(5), opt is now None

    // replace: Replace value, returning old
    let mut opt2 = Some(5);
    let old = opt2.replace(10);  // Some(5), opt2 is Some(10)
}

// Combining Option with iterators
fn get_first_even(numbers: &[i32]) -> Option<i32> {
    numbers.iter()
        .copied()
        .find(|n| n % 2 == 0)
}

// Optional chaining with ?
fn get_nested_value(data: &Option<Vec<Option<i32>>>) -> Option<i32> {
    let vec = data.as_ref()?;
    let first = vec.first()?;
    *first
}

Custom Error Types

For robust error handling, define custom error types:

use std::fmt;
use std::error::Error;

// Custom error enum with multiple variants
#[derive(Debug)]
enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
    Validation { field: String, message: String },
    NotFound(String),
    Unauthorized,
    RateLimit { retry_after: u64 },
}

// Implement Display for user-friendly messages
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "I/O error: {}", e),
            AppError::Parse(e) => write!(f, "Parse error: {}", e),
            AppError::Validation { field, message } => {
                write!(f, "Validation failed for '{}': {}", field, message)
            }
            AppError::NotFound(resource) => {
                write!(f, "Resource not found: {}", resource)
            }
            AppError::Unauthorized => write!(f, "Unauthorized access"),
            AppError::RateLimit { retry_after } => {
                write!(f, "Rate limited. Retry after {} seconds", retry_after)
            }
        }
    }
}

// Implement Error trait for standard error handling
impl Error for AppError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            AppError::Io(e) => Some(e),
            AppError::Parse(e) => Some(e),
            _ => None,
        }
    }
}

// Implement From for automatic conversion with ?
impl From<std::io::Error> for AppError {
    fn from(err: std::io::Error) -> Self {
        AppError::Io(err)
    }
}

impl From<std::num::ParseIntError> for AppError {
    fn from(err: std::num::ParseIntError) -> Self {
        AppError::Parse(err)
    }
}

// Usage example
fn process_config(path: &str) -> Result<i32, AppError> {
    let contents = std::fs::read_to_string(path)?;  // Auto-converts io::Error
    let value: i32 = contents.trim().parse()?;  // Auto-converts ParseIntError

    if value < 0 {
        return Err(AppError::Validation {
            field: "config_value".to_string(),
            message: "must be non-negative".to_string(),
        });
    }

    Ok(value)
}

// Printing error chain
fn print_error_chain(err: &dyn Error) {
    eprintln!("Error: {}", err);
    let mut source = err.source();
    while let Some(cause) = source {
        eprintln!("Caused by: {}", cause);
        source = cause.source();
    }
}

Using thiserror for Library Errors

The thiserror crate reduces boilerplate for custom errors:

// Add to Cargo.toml: thiserror = "1.0"
use thiserror::Error;

#[derive(Error, Debug)]
enum DataError {
    #[error("Failed to read data from {path}: {source}")]
    ReadError {
        path: String,
        #[source]
        source: std::io::Error,
    },

    #[error("Failed to parse data: {0}")]
    ParseError(#[from] serde_json::Error),

    #[error("Invalid data format: expected {expected}, got {actual}")]
    FormatError {
        expected: String,
        actual: String,
    },

    #[error("Data validation failed")]
    ValidationError(#[source] ValidationError),

    #[error(transparent)]  // Delegate Display to inner error
    Other(#[from] anyhow::Error),
}

#[derive(Error, Debug)]
#[error("Validation error in field '{field}': {message}")]
struct ValidationError {
    field: String,
    message: String,
}

Using anyhow for Application Errors

The anyhow crate simplifies error handling in applications:

// Add to Cargo.toml: anyhow = "1.0"
use anyhow::{Context, Result, bail, ensure, anyhow};

// Result is anyhow::Result<T> = std::result::Result<T, anyhow::Error>
fn load_config(path: &str) -> Result<Config> {
    let contents = std::fs::read_to_string(path)
        .context(format!("Failed to read config from {}", path))?;

    let config: Config = serde_json::from_str(&contents)
        .context("Failed to parse config JSON")?;

    // Validation with bail! macro
    if config.timeout == 0 {
        bail!("Config timeout must be non-zero");
    }

    // Validation with ensure! macro
    ensure!(config.max_connections > 0, "max_connections must be positive");

    Ok(config)
}

// Creating ad-hoc errors
fn validate_input(input: &str) -> Result<()> {
    if input.is_empty() {
        return Err(anyhow!("Input cannot be empty"));
    }

    if input.len() > 1000 {
        bail!("Input too long: {} chars (max 1000)", input.len());
    }

    Ok(())
}

// Downcasting to specific error types
fn handle_error(err: anyhow::Error) {
    if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
        eprintln!("I/O Error: {}", io_err);
    } else if let Some(parse_err) = err.downcast_ref::<serde_json::Error>() {
        eprintln!("JSON Error: {}", parse_err);
    } else {
        eprintln!("Unknown error: {}", err);
    }

    // Print full error chain
    eprintln!("\nError chain:");
    for (i, cause) in err.chain().enumerate() {
        eprintln!("  {}: {}", i, cause);
    }
}

#[derive(serde::Deserialize)]
struct Config {
    timeout: u64,
    max_connections: u32,
}

Error Handling Patterns and Best Practices

use std::fs::File;

// Pattern 1: unwrap/expect for prototypes and tests
fn quick_prototype() {
    let file = File::open("data.txt").unwrap();
    let config = load_config().expect("Config must exist");
}

// Pattern 2: Match for granular control
fn match_handling() {
    let file = match File::open("hello.txt") {
        Ok(file) => file,
        Err(error) => match error.kind() {
            std::io::ErrorKind::NotFound => {
                match File::create("hello.txt") {
                    Ok(fc) => fc,
                    Err(e) => panic!("Cannot create file: {:?}", e),
                }
            }
            other_error => {
                panic!("Cannot open file: {:?}", other_error);
            }
        },
    };
}

// Pattern 3: Combinators for functional style
fn combinator_style() -> Result<String, std::io::Error> {
    std::fs::read_to_string("config.txt")
        .map(|s| s.trim().to_uppercase())
        .or_else(|_| Ok("DEFAULT".to_string()))
}

// Pattern 4: Early return with ?
fn early_return(path: &str) -> Result<Data, AppError> {
    let file = File::open(path)?;
    let contents = read_contents(file)?;
    let parsed = parse_data(&contents)?;
    validate(&parsed)?;
    Ok(parsed)
}

// Pattern 5: Collecting Results
fn process_all_files(paths: &[&str]) -> Result<Vec<String>, std::io::Error> {
    paths.iter()
        .map(|path| std::fs::read_to_string(path))
        .collect()  // Collects into Result<Vec<String>, Error>
}

// Pattern 6: partition_map for separating successes and failures
fn process_with_partial_failure(inputs: Vec<&str>) -> (Vec<i32>, Vec<String>) {
    let (successes, failures): (Vec<_>, Vec<_>) = inputs
        .into_iter()
        .map(|s| s.parse::<i32>())
        .partition(Result::is_ok);

    let values: Vec<i32> = successes.into_iter().map(Result::unwrap).collect();
    let errors: Vec<String> = failures.into_iter()
        .map(|r| r.unwrap_err().to_string())
        .collect();

    (values, errors)
}

// When to use panic! vs Result
// - panic!: Programming errors, violated invariants, unrecoverable states
// - Result: Expected failures, user input errors, external system failures

fn safe_divide(a: i32, b: i32) -> Result<i32, &'static str> {
    if b == 0 {
        Err("Division by zero")
    } else {
        Ok(a / b)
    }
}

// Panic for invariant violations (should never happen if code is correct)
fn get_element(vec: &[i32], index: usize) -> i32 {
    assert!(index < vec.len(), "Index out of bounds: BUG in caller");
    vec[index]
}

struct Data;
fn load_config() -> Result<(), ()> { Ok(()) }
fn read_contents(_: File) -> Result<Data, AppError> { Ok(Data) }
fn parse_data(_: &Data) -> Result<Data, AppError> { Ok(Data) }
fn validate(_: &Data) -> Result<(), AppError> { Ok(()) }

4. Smart Pointers

Smart pointers are data structures that act like pointers but include additional metadata and capabilities. They implement the Deref trait (allowing them to be used like references) and the Drop trait (for custom cleanup logic). Rust's ownership system ensures smart pointers are used safely, preventing memory leaks and dangling pointers.

Common Smart Pointers:

Type Purpose Thread Safety Overhead
Box<T> Heap allocation, single ownership Send + Sync if T is Minimal (pointer only)
Rc<T> Reference counting, shared ownership Single-threaded only Reference count
Arc<T> Atomic reference counting Thread-safe Atomic reference count
Cell<T> Interior mutability (Copy types) Single-threaded None
RefCell<T> Interior mutability with runtime checks Single-threaded Borrow state tracking
Mutex<T> Thread-safe interior mutability Thread-safe Lock overhead
RwLock<T> Multiple readers OR single writer Thread-safe Lock overhead
Cow<T> Clone-on-write Depends on T Enum discriminant

Box - Heap Allocation

Box<T> provides the simplest form of heap allocation with single ownership:

// Basic heap allocation
let boxed: Box<i32> = Box::new(5);
println!("boxed = {}", boxed);

// Use cases for Box:
// 1. Types with unknown size at compile time (trait objects)
// 2. Large data you want to transfer ownership without copying
// 3. Recursive types

// Recursive type: Linked list
#[derive(Debug)]
enum List<T> {
    Cons(T, Box<List<T>>),
    Nil,
}

use List::{Cons, Nil};

fn create_list() -> List<i32> {
    Cons(1, Box::new(
        Cons(2, Box::new(
            Cons(3, Box::new(Nil))
        ))
    ))
}

// Box for trait objects
trait Animal {
    fn speak(&self);
}

struct Dog { name: String }
struct Cat { name: String }

impl Animal for Dog {
    fn speak(&self) { println!("{} says woof!", self.name); }
}

impl Animal for Cat {
    fn speak(&self) { println!("{} says meow!", self.name); }
}

fn get_pet(is_dog: bool) -> Box<dyn Animal> {
    if is_dog {
        Box::new(Dog { name: "Buddy".to_string() })
    } else {
        Box::new(Cat { name: "Whiskers".to_string() })
    }
}

// Box::leak for static references
fn create_static_string(s: String) -> &'static str {
    Box::leak(s.into_boxed_str())
}

// Box with custom allocators (nightly)
// let boxed = Box::new_in(42, CustomAllocator);

fn main() {
    let list = create_list();
    println!("{:?}", list);

    let pet = get_pet(true);
    pet.speak();
}

Rc - Reference Counting

Rc<T> enables shared ownership through reference counting (single-threaded only):

use std::rc::Rc;

// Basic Rc usage
fn demonstrate_rc() {
    let a = Rc::new(5);
    println!("Count after creating a: {}", Rc::strong_count(&a));

    let b = Rc::clone(&a);  // Increments count, doesn't deep clone
    println!("Count after cloning to b: {}", Rc::strong_count(&a));

    {
        let c = Rc::clone(&a);
        println!("Count after cloning to c: {}", Rc::strong_count(&a));
    }

    println!("Count after c goes out of scope: {}", Rc::strong_count(&a));
}

// Shared ownership example: Multiple nodes sharing a subgraph
#[derive(Debug)]
struct Node {
    value: i32,
    children: Vec<Rc<Node>>,
}

fn build_shared_graph() {
    // Shared node
    let shared_node = Rc::new(Node {
        value: 3,
        children: vec![]
    });

    let node_a = Rc::new(Node {
        value: 1,
        children: vec![Rc::clone(&shared_node)],
    });

    let node_b = Rc::new(Node {
        value: 2,
        children: vec![Rc::clone(&shared_node)],
    });

    println!("Shared node has {} strong references",
             Rc::strong_count(&shared_node));
}

// Rc::make_mut for copy-on-write semantics
fn copy_on_write() {
    let mut data = Rc::new(vec![1, 2, 3]);

    let data2 = Rc::clone(&data);

    // make_mut will clone if there are other references
    Rc::make_mut(&mut data).push(4);

    println!("data: {:?}", data);   // [1, 2, 3, 4]
    println!("data2: {:?}", data2); // [1, 2, 3]
}

// Rc::try_unwrap - attempt to take ownership
fn try_unwrap_example() {
    let rc = Rc::new("hello".to_string());

    match Rc::try_unwrap(rc) {
        Ok(value) => println!("Got ownership: {}", value),
        Err(rc) => println!("Still shared: {}", rc),
    }
}

Weak - Weak References

Weak<T> provides non-owning references to break reference cycles:

use std::rc::{Rc, Weak};
use std::cell::RefCell;

// Parent-child relationship with weak back-reference
#[derive(Debug)]
struct TreeNode {
    value: i32,
    parent: RefCell<Weak<TreeNode>>,  // Weak reference to parent
    children: RefCell<Vec<Rc<TreeNode>>>,
}

fn build_tree() {
    let leaf = Rc::new(TreeNode {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!("leaf strong = {}, weak = {}",
             Rc::strong_count(&leaf), Rc::weak_count(&leaf));

    {
        let branch = Rc::new(TreeNode {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });

        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

        println!("branch strong = {}, weak = {}",
                 Rc::strong_count(&branch), Rc::weak_count(&branch));
        println!("leaf parent = {:?}",
                 leaf.parent.borrow().upgrade().map(|n| n.value));
    }

    // branch is dropped, weak reference returns None
    println!("leaf parent after branch dropped = {:?}",
             leaf.parent.borrow().upgrade());
}

// Observer pattern with weak references
struct Observable {
    observers: Vec<Weak<dyn Observer>>,
}

trait Observer {
    fn update(&self, data: &str);
}

impl Observable {
    fn add_observer(&mut self, observer: Weak<dyn Observer>) {
        self.observers.push(observer);
    }

    fn notify(&mut self, data: &str) {
        // Clean up dead observers and notify living ones
        self.observers.retain(|weak| {
            if let Some(observer) = weak.upgrade() {
                observer.update(data);
                true
            } else {
                false  // Remove dead reference
            }
        });
    }
}

Cell and RefCell - Interior Mutability

Interior mutability allows mutation through shared references:

use std::cell::{Cell, RefCell, Ref, RefMut};

// Cell<T> - for Copy types, replaces value entirely
fn cell_example() {
    let cell = Cell::new(5);

    // No borrowing - just get and set
    let value = cell.get();
    cell.set(value + 1);

    println!("Value: {}", cell.get());

    // swap and replace
    let old = cell.replace(10);
    println!("Old: {}, New: {}", old, cell.get());

    // Multiple references can exist
    let ref1 = &cell;
    let ref2 = &cell;
    ref1.set(20);
    ref2.set(30);  // Both can mutate!
}

// RefCell<T> - for any type, runtime borrow checking
fn refcell_example() {
    let data = RefCell::new(vec![1, 2, 3]);

    // Borrow immutably
    {
        let borrowed: Ref<Vec<i32>> = data.borrow();
        println!("Data: {:?}", *borrowed);
    }

    // Borrow mutably
    {
        let mut borrowed: RefMut<Vec<i32>> = data.borrow_mut();
        borrowed.push(4);
    }

    // try_borrow and try_borrow_mut for fallible borrowing
    if let Ok(borrowed) = data.try_borrow() {
        println!("Successfully borrowed: {:?}", *borrowed);
    }

    // This would panic at runtime!
    // let borrow1 = data.borrow();
    // let borrow2 = data.borrow_mut();  // panic!
}

// Common pattern: Rc<RefCell<T>> for shared mutable data
fn shared_mutable_data() {
    use std::rc::Rc;

    let shared = Rc::new(RefCell::new(vec![1, 2, 3]));

    let clone1 = Rc::clone(&shared);
    let clone2 = Rc::clone(&shared);

    clone1.borrow_mut().push(4);
    clone2.borrow_mut().push(5);

    println!("Shared data: {:?}", shared.borrow());
}

// OnceCell - write once, read many
use std::cell::OnceCell;

fn once_cell_example() {
    let cell: OnceCell<String> = OnceCell::new();

    assert!(cell.get().is_none());

    let value = cell.get_or_init(|| {
        "computed once".to_string()
    });

    assert_eq!(value, "computed once");

    // Second call returns cached value
    let value2 = cell.get_or_init(|| {
        "this won't run".to_string()
    });

    assert_eq!(value2, "computed once");
}

Arc - Atomic Reference Counting

Arc<T> is thread-safe version of Rc<T>:

use std::sync::Arc;
use std::thread;

fn basic_arc() {
    let data = Arc::new(vec![1, 2, 3]);

    let handles: Vec<_> = (0..3)
        .map(|i| {
            let data = Arc::clone(&data);
            thread::spawn(move || {
                println!("Thread {} sees: {:?}", i, data);
            })
        })
        .collect();

    for handle in handles {
        handle.join().unwrap();
    }
}

// Arc with Mutex for shared mutable state
use std::sync::Mutex;

fn arc_mutex() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

// Arc with RwLock for read-heavy workloads
use std::sync::RwLock;

fn arc_rwlock() {
    let data = Arc::new(RwLock::new(vec![1, 2, 3]));

    // Multiple readers simultaneously
    let readers: Vec<_> = (0..5)
        .map(|i| {
            let data = Arc::clone(&data);
            thread::spawn(move || {
                let read_guard = data.read().unwrap();
                println!("Reader {} sees: {:?}", i, *read_guard);
            })
        })
        .collect();

    // Single writer
    {
        let mut write_guard = data.write().unwrap();
        write_guard.push(4);
    }

    for handle in readers {
        handle.join().unwrap();
    }
}

Cow - Clone on Write

Cow (Clone on Write) is an enum that can hold either borrowed or owned data:

use std::borrow::Cow;

// Function that may or may not need to modify input
fn process_text(input: &str) -> Cow<str> {
    if input.contains("bad") {
        // Need to modify - return owned
        Cow::Owned(input.replace("bad", "good"))
    } else {
        // No modification needed - return borrowed
        Cow::Borrowed(input)
    }
}

fn cow_example() {
    let text1 = "hello world";
    let text2 = "this is bad text";

    let result1 = process_text(text1);
    let result2 = process_text(text2);

    // Can check if data was cloned
    match &result1 {
        Cow::Borrowed(_) => println!("result1 is borrowed (no allocation)"),
        Cow::Owned(_) => println!("result1 is owned (allocated)"),
    }

    // Use like a string slice
    println!("Result 1: {}", result1);
    println!("Result 2: {}", result2);

    // Convert to owned if needed
    let owned: String = result1.into_owned();
}

// Cow in function parameters
fn append_if_needed<'a>(s: &'a str, suffix: &str, condition: bool) -> Cow<'a, str> {
    if condition {
        Cow::Owned(format!("{}{}", s, suffix))
    } else {
        Cow::Borrowed(s)
    }
}

// Cow for efficient string building
fn build_path(base: &str, segments: &[&str]) -> Cow<str> {
    if segments.is_empty() {
        Cow::Borrowed(base)
    } else {
        let mut path = base.to_string();
        for segment in segments {
            path.push('/');
            path.push_str(segment);
        }
        Cow::Owned(path)
    }
}

Pin - Memory Pinning

Pin guarantees that the pointed-to value won't be moved in memory:

use std::pin::Pin;
use std::marker::PhantomPinned;

// Self-referential struct (needs pinning)
struct SelfReferential {
    value: String,
    // Points to value field - becomes invalid if struct moves!
    self_ptr: *const String,
    _marker: PhantomPinned,  // Makes type !Unpin
}

impl SelfReferential {
    fn new(value: String) -> Pin<Box<Self>> {
        let mut boxed = Box::new(SelfReferential {
            value,
            self_ptr: std::ptr::null(),
            _marker: PhantomPinned,
        });

        let self_ptr: *const String = &boxed.value;

        // Safe because we're initializing before pinning
        unsafe {
            let mut_ref: Pin<&mut Self> = Pin::new_unchecked(&mut *boxed);
            Pin::get_unchecked_mut(mut_ref).self_ptr = self_ptr;
        }

        unsafe { Pin::new_unchecked(boxed) }
    }

    fn value(self: Pin<&Self>) -> &str {
        &self.value
    }

    fn self_ptr_value(self: Pin<&Self>) -> &str {
        unsafe { &*self.self_ptr }
    }
}

// Pin is crucial for async/await
// Future trait requires Pin<&mut Self> for poll()
use std::future::Future;
use std::task::{Context, Poll};

struct MyFuture {
    // async state machine fields
}

impl Future for MyFuture {
    type Output = i32;

    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
        Poll::Ready(42)
    }
}

// Most types are Unpin (can be safely moved after pinning)
fn unpin_types() {
    let mut x = 5;
    let pinned = Pin::new(&mut x);

    // Unpin types can be unpinned
    let unpinned: &mut i32 = Pin::into_inner(pinned);
    *unpinned = 10;
}

5. Concurrency

Rust's ownership system prevents data races at compile time, enabling what's called "fearless concurrency." The compiler enforces that either multiple threads can read data, OR one thread can mutate it—never both simultaneously. This eliminates entire classes of bugs that plague other languages.

Concurrency Primitives:

Primitive Purpose Key Characteristics
thread::spawn Create OS threads Heavy-weight, 1:1 mapping
thread::scope Scoped threads Can borrow from parent
mpsc::channel Multi-producer, single-consumer Message passing
Mutex<T> Mutual exclusion One accessor at a time
RwLock<T> Read-write lock Many readers OR one writer
Condvar Condition variable Wait for conditions
Barrier Synchronization point Wait for all threads
atomic types Lock-free operations AtomicBool, AtomicUsize, etc.

Threads and Thread Spawning

use std::thread;
use std::time::Duration;

fn basic_threads() {
    // Spawn a thread - returns JoinHandle
    let handle = thread::spawn(|| {
        for i in 1..=5 {
            println!("Spawned thread: count {}", i);
            thread::sleep(Duration::from_millis(100));
        }
        42  // Return value
    });

    // Main thread continues
    for i in 1..=3 {
        println!("Main thread: count {}", i);
        thread::sleep(Duration::from_millis(150));
    }

    // Wait for thread and get result
    let result = handle.join().unwrap();
    println!("Thread returned: {}", result);
}

// Moving data into threads
fn thread_with_move() {
    let data = vec![1, 2, 3];

    // move keyword transfers ownership
    let handle = thread::spawn(move || {
        println!("Data in thread: {:?}", data);
        data.iter().sum::<i32>()
    });

    // data is no longer accessible here
    let sum = handle.join().unwrap();
    println!("Sum: {}", sum);
}

// Named threads and thread builder
fn thread_builder() {
    let builder = thread::Builder::new()
        .name("worker".into())
        .stack_size(4 * 1024 * 1024);  // 4MB stack

    let handle = builder.spawn(|| {
        println!("Thread name: {:?}", thread::current().name());
    }).unwrap();

    handle.join().unwrap();
}

// Get current thread info
fn thread_info() {
    println!("Current thread: {:?}", thread::current().name());
    println!("Thread ID: {:?}", thread::current().id());

    // Yield to other threads
    thread::yield_now();

    // Sleep
    thread::sleep(Duration::from_millis(100));

    // Get number of CPUs
    let cpus = thread::available_parallelism()
        .map(|n| n.get())
        .unwrap_or(1);
    println!("Available parallelism: {}", cpus);
}

Scoped Threads

Scoped threads can borrow from the parent stack without requiring 'static:

use std::thread;

fn scoped_threads() {
    let data = vec![1, 2, 3, 4, 5];
    let mut results = vec![0; 5];

    thread::scope(|s| {
        // These threads can borrow data and results
        for (i, value) in data.iter().enumerate() {
            let result_slot = &mut results[i];

            s.spawn(move || {
                // Can borrow from parent scope!
                *result_slot = value * 2;
            });
        }
    });  // All threads joined here

    println!("Results: {:?}", results);
}

// Parallel map with scoped threads
fn parallel_map<T, R, F>(items: &[T], f: F) -> Vec<R>
where
    T: Sync,
    R: Send + Default + Clone,
    F: Fn(&T) -> R + Sync,
{
    let mut results = vec![R::default(); items.len()];

    thread::scope(|s| {
        for (item, result) in items.iter().zip(results.iter_mut()) {
            s.spawn(|| {
                *result = f(item);
            });
        }
    });

    results
}

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let squared = parallel_map(&numbers, |x| x * x);
    println!("Squared: {:?}", squared);
}

Message Passing with Channels

Channels provide safe communication between threads:

use std::sync::mpsc::{self, Sender, Receiver};
use std::thread;
use std::time::Duration;

// Basic channel usage
fn basic_channel() {
    let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();

    thread::spawn(move || {
        let messages = vec!["hello", "from", "the", "thread"];
        for msg in messages {
            tx.send(msg.to_string()).unwrap();
            thread::sleep(Duration::from_millis(100));
        }
    });

    // Receive messages
    for received in rx {
        println!("Got: {}", received);
    }
}

// Multiple producers
fn multiple_producers() {
    let (tx, rx) = mpsc::channel();

    for i in 0..3 {
        let tx_clone = tx.clone();
        thread::spawn(move || {
            for j in 0..3 {
                tx_clone.send(format!("Thread {} message {}", i, j)).unwrap();
                thread::sleep(Duration::from_millis(50));
            }
        });
    }

    drop(tx);  // Drop original sender so rx knows when to stop

    for msg in rx {
        println!("{}", msg);
    }
}

// Non-blocking receive
fn non_blocking() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        thread::sleep(Duration::from_millis(100));
        tx.send(42).unwrap();
    });

    loop {
        match rx.try_recv() {
            Ok(value) => {
                println!("Received: {}", value);
                break;
            }
            Err(mpsc::TryRecvError::Empty) => {
                println!("No message yet, doing other work...");
                thread::sleep(Duration::from_millis(20));
            }
            Err(mpsc::TryRecvError::Disconnected) => {
                println!("Channel closed");
                break;
            }
        }
    }
}

// Receive with timeout
fn with_timeout() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        thread::sleep(Duration::from_millis(500));
        let _ = tx.send(42);
    });

    match rx.recv_timeout(Duration::from_millis(100)) {
        Ok(value) => println!("Received: {}", value),
        Err(mpsc::RecvTimeoutError::Timeout) => println!("Timed out"),
        Err(mpsc::RecvTimeoutError::Disconnected) => println!("Channel closed"),
    }
}

// Bounded channel (sync_channel)
fn bounded_channel() {
    // Buffer size of 2
    let (tx, rx) = mpsc::sync_channel(2);

    thread::spawn(move || {
        for i in 0..5 {
            println!("Sending {}", i);
            tx.send(i).unwrap();  // Blocks when buffer full
            println!("Sent {}", i);
        }
    });

    thread::sleep(Duration::from_millis(500));

    for received in rx {
        println!("Received: {}", received);
    }
}

Mutex and Shared State

use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;

fn basic_mutex() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            // lock() returns MutexGuard, unlocks on drop
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

// Handling poisoned mutex
fn handle_poisoning() {
    let mutex = Arc::new(Mutex::new(0));
    let mutex_clone = Arc::clone(&mutex);

    let handle = thread::spawn(move || {
        let _guard = mutex_clone.lock().unwrap();
        panic!("Thread panicked while holding lock!");
    });

    let _ = handle.join();  // Thread panicked

    // Mutex is now poisoned
    match mutex.lock() {
        Ok(guard) => println!("Got lock: {}", *guard),
        Err(poisoned) => {
            // Can still access data, but know it might be inconsistent
            let guard = poisoned.into_inner();
            println!("Recovered from poisoned mutex: {}", *guard);
        }
    }
}

// try_lock for non-blocking
fn try_lock_example() {
    let mutex = Arc::new(Mutex::new(0));
    let mutex_clone = Arc::clone(&mutex);

    // Hold the lock
    let _guard = mutex.lock().unwrap();

    let handle = thread::spawn(move || {
        match mutex_clone.try_lock() {
            Ok(guard) => println!("Got lock: {}", *guard),
            Err(_) => println!("Lock is held by another thread"),
        }
    });

    handle.join().unwrap();
}

RwLock for Read-Heavy Workloads

use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;

fn rwlock_example() {
    let data = Arc::new(RwLock::new(vec![1, 2, 3]));

    // Multiple readers can access simultaneously
    let readers: Vec<_> = (0..5)
        .map(|i| {
            let data = Arc::clone(&data);
            thread::spawn(move || {
                for _ in 0..3 {
                    let guard = data.read().unwrap();
                    println!("Reader {} sees: {:?}", i, *guard);
                    thread::sleep(Duration::from_millis(10));
                }
            })
        })
        .collect();

    // Writer gets exclusive access
    let data_clone = Arc::clone(&data);
    let writer = thread::spawn(move || {
        for i in 4..7 {
            let mut guard = data_clone.write().unwrap();
            guard.push(i);
            println!("Writer added {}", i);
            thread::sleep(Duration::from_millis(50));
        }
    });

    for reader in readers {
        reader.join().unwrap();
    }
    writer.join().unwrap();

    println!("Final data: {:?}", *data.read().unwrap());
}

Condition Variables

use std::sync::{Arc, Mutex, Condvar};
use std::thread;
use std::time::Duration;

fn condvar_example() {
    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair_clone = Arc::clone(&pair);

    // Spawned thread waits for signal
    let handle = thread::spawn(move || {
        let (lock, cvar) = &*pair_clone;
        let mut started = lock.lock().unwrap();

        while !*started {
            // Wait releases lock and sleeps until notified
            started = cvar.wait(started).unwrap();
        }

        println!("Worker: received signal, starting work");
    });

    // Main thread signals
    thread::sleep(Duration::from_millis(100));
    println!("Main: sending signal");

    let (lock, cvar) = &*pair;
    {
        let mut started = lock.lock().unwrap();
        *started = true;
    }
    cvar.notify_one();

    handle.join().unwrap();
}

// Producer-consumer with condvar
fn producer_consumer() {
    let queue = Arc::new((Mutex::new(Vec::new()), Condvar::new()));

    let producer_queue = Arc::clone(&queue);
    let producer = thread::spawn(move || {
        for i in 0..5 {
            let (lock, cvar) = &*producer_queue;
            let mut queue = lock.lock().unwrap();
            queue.push(i);
            println!("Produced: {}", i);
            cvar.notify_one();
            drop(queue);
            thread::sleep(Duration::from_millis(100));
        }
    });

    let consumer_queue = Arc::clone(&queue);
    let consumer = thread::spawn(move || {
        for _ in 0..5 {
            let (lock, cvar) = &*consumer_queue;
            let mut queue = lock.lock().unwrap();

            while queue.is_empty() {
                queue = cvar.wait(queue).unwrap();
            }

            let item = queue.remove(0);
            println!("Consumed: {}", item);
        }
    });

    producer.join().unwrap();
    consumer.join().unwrap();
}

Barrier for Synchronization

use std::sync::{Arc, Barrier};
use std::thread;

fn barrier_example() {
    let num_threads = 5;
    let barrier = Arc::new(Barrier::new(num_threads));

    let handles: Vec<_> = (0..num_threads)
        .map(|i| {
            let barrier = Arc::clone(&barrier);
            thread::spawn(move || {
                println!("Thread {} doing setup work", i);
                thread::sleep(std::time::Duration::from_millis(i as u64 * 100));

                // Wait for all threads
                println!("Thread {} waiting at barrier", i);
                barrier.wait();

                // All threads continue together
                println!("Thread {} passed barrier", i);
            })
        })
        .collect();

    for handle in handles {
        handle.join().unwrap();
    }
}

Atomic Operations

Lock-free operations for simple shared state:

use std::sync::atomic::{AtomicBool, AtomicUsize, AtomicI64, Ordering};
use std::sync::Arc;
use std::thread;

fn atomic_example() {
    let counter = Arc::new(AtomicUsize::new(0));
    let running = Arc::new(AtomicBool::new(true));

    let handles: Vec<_> = (0..4)
        .map(|_| {
            let counter = Arc::clone(&counter);
            let running = Arc::clone(&running);

            thread::spawn(move || {
                while running.load(Ordering::Relaxed) {
                    // Atomic increment
                    counter.fetch_add(1, Ordering::SeqCst);
                    thread::yield_now();
                }
            })
        })
        .collect();

    thread::sleep(std::time::Duration::from_millis(10));
    running.store(false, Ordering::Relaxed);

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final count: {}", counter.load(Ordering::SeqCst));
}

// Compare-and-swap for lock-free data structures
fn compare_and_swap() {
    let value = AtomicI64::new(5);

    // Only update if current value matches expected
    let result = value.compare_exchange(
        5,                  // expected
        10,                 // new value
        Ordering::SeqCst,   // success ordering
        Ordering::SeqCst,   // failure ordering
    );

    match result {
        Ok(old) => println!("Updated from {} to 10", old),
        Err(current) => println!("Failed, current value is {}", current),
    }

    // fetch_update for conditional atomic updates
    let result = value.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
        if x > 0 { Some(x - 1) } else { None }
    });
}

// Memory orderings explained:
// - Relaxed: No synchronization, just atomicity
// - Acquire: Reads can't move before this load
// - Release: Writes can't move after this store
// - AcqRel: Both acquire and release
// - SeqCst: Strongest guarantee, total ordering

Send and Sync Traits

These marker traits determine thread safety:

use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;

// Send: Type can be transferred to another thread
// Sync: Type can be shared between threads (&T is Send)

// Most primitive types are both Send and Sync
fn send_sync_primitives() {
    let x: i32 = 5;  // Send + Sync
    let s: String = "hello".to_string();  // Send + Sync
    let v: Vec<i32> = vec![1, 2, 3];  // Send + Sync
}

// Rc is neither Send nor Sync
fn rc_not_thread_safe() {
    let rc = Rc::new(5);

    // This won't compile:
    // thread::spawn(move || {
    //     println!("{}", rc);
    // });
}

// Arc is both Send and Sync
fn arc_is_thread_safe() {
    let arc = Arc::new(5);
    let arc_clone = Arc::clone(&arc);

    std::thread::spawn(move || {
        println!("{}", arc_clone);
    }).join().unwrap();
}

// RefCell is Send but not Sync
fn refcell_is_send() {
    let refcell = RefCell::new(5);

    // Can move to another thread
    std::thread::spawn(move || {
        *refcell.borrow_mut() += 1;
        println!("{}", refcell.borrow());
    }).join().unwrap();
}

// Mutex<T> is Send + Sync if T is Send
fn mutex_thread_safety() {
    use std::sync::Mutex;

    // Mutex<i32> is Send + Sync
    let mutex = Arc::new(Mutex::new(5));

    // Can be shared across threads
    let mutex_clone = Arc::clone(&mutex);
    std::thread::spawn(move || {
        *mutex_clone.lock().unwrap() += 1;
    }).join().unwrap();
}

// Raw pointers are neither Send nor Sync
// Use unsafe to opt-in when you know it's safe
struct SendWrapper<T>(*mut T);

// SAFETY: We ensure T is only accessed from one thread at a time
unsafe impl<T: Send> Send for SendWrapper<T> {}

6. Macros

Macros enable metaprogramming—writing code that generates other code at compile time. Rust macros are more structured and safer than C preprocessor macros. They operate on the Abstract Syntax Tree (AST) rather than raw text, preventing many common macro-related bugs.

Types of Macros:

Type Syntax Use Case Crate Requirements
Declarative (macro_rules!) Pattern matching Simple code generation None
Derive macros #[derive(MyTrait)] Auto-implement traits Separate proc-macro crate
Attribute macros #[my_attribute] Transform items Separate proc-macro crate
Function-like macros my_macro!(...) Custom syntax Separate proc-macro crate

Declarative Macros (macro_rules!)

Declarative macros use pattern matching on Rust syntax:

// Basic macro: create a vector
macro_rules! vec {
    // Empty case
    () => {
        Vec::new()
    };
    // With elements
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
    // With trailing comma
    ( $( $x:expr ),+ , ) => {
        vec![$( $x ),*]
    };
}

// Fragment types:
// $x:expr     - expression
// $x:stmt     - statement
// $x:ty       - type
// $x:ident    - identifier
// $x:path     - path (like std::vec::Vec)
// $x:pat      - pattern
// $x:block    - block expression
// $x:item     - item (fn, struct, etc.)
// $x:meta     - meta item (attributes)
// $x:tt       - token tree (anything)
// $x:literal  - literal value

// Repetition operators:
// $(...)*     - zero or more
// $(...)+     - one or more
// $(...)?     - zero or one

// Implementing a hashmap! macro
macro_rules! hashmap {
    () => {
        std::collections::HashMap::new()
    };
    ( $( $key:expr => $value:expr ),+ $(,)? ) => {
        {
            let mut map = std::collections::HashMap::new();
            $(
                map.insert($key, $value);
            )+
            map
        }
    };
}

fn use_hashmap() {
    let map = hashmap! {
        "name" => "Alice",
        "city" => "Boston",
    };
    println!("{:?}", map);
}

// Debug/trace macro
macro_rules! dbg_verbose {
    ($val:expr) => {
        match $val {
            tmp => {
                eprintln!(
                    "[{}:{}] {} = {:?}",
                    file!(),
                    line!(),
                    stringify!($val),
                    &tmp
                );
                tmp
            }
        }
    };
}

// Macro with multiple pattern arms
macro_rules! calculate {
    // Addition
    (add $a:expr, $b:expr) => {
        $a + $b
    };
    // Multiplication
    (mul $a:expr, $b:expr) => {
        $a * $b
    };
    // Power (recursive)
    (pow $base:expr, $exp:expr) => {
        {
            let mut result = 1;
            for _ in 0..$exp {
                result *= $base;
            }
            result
        }
    };
}

fn use_calculate() {
    let sum = calculate!(add 5, 3);       // 8
    let product = calculate!(mul 5, 3);   // 15
    let power = calculate!(pow 2, 10);    // 1024
}

// Recursive macro for nested structures
macro_rules! nested_vec {
    // Base case: single element
    ($elem:expr) => {
        vec![$elem]
    };
    // Recursive case: nested vectors
    ([ $( $inner:tt ),* ]) => {
        vec![ $( nested_vec!($inner) ),* ]
    };
}

// TT munching pattern for complex parsing
macro_rules! count_exprs {
    () => { 0 };
    ($head:expr) => { 1 };
    ($head:expr, $($tail:expr),*) => {
        1 + count_exprs!($($tail),*)
    };
}

fn use_count() {
    let count = count_exprs!(1, 2, 3, 4, 5);  // 5
}

// Macro for struct builder pattern
macro_rules! builder {
    ($name:ident { $( $field:ident : $type:ty ),* $(,)? }) => {
        #[derive(Default)]
        struct $name {
            $( $field: Option<$type>, )*
        }

        impl $name {
            fn new() -> Self {
                Self::default()
            }

            $(
                fn $field(mut self, value: $type) -> Self {
                    self.$field = Some(value);
                    self
                }
            )*
        }
    };
}

builder!(PersonBuilder {
    name: String,
    age: u32,
    email: String,
});

fn use_builder() {
    let person = PersonBuilder::new()
        .name("Alice".to_string())
        .age(30)
        .email("alice@example.com".to_string());
}

Procedural Macros

Procedural macros are more powerful, operating on TokenStreams. They require a separate crate with proc-macro = true in Cargo.toml.

Derive Macros
// In Cargo.toml of proc-macro crate:
// [lib]
// proc-macro = true
//
// [dependencies]
// syn = { version = "2", features = ["full"] }
// quote = "1"
// proc-macro2 = "1"

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};

// Derive macro that implements a trait
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }
        }
    };

    gen.into()
}

// Derive macro with field inspection
#[proc_macro_derive(Describe)]
pub fn describe_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let description = match &ast.data {
        Data::Struct(data_struct) => {
            let field_names: Vec<_> = match &data_struct.fields {
                Fields::Named(fields) => {
                    fields.named.iter()
                        .map(|f| f.ident.as_ref().unwrap().to_string())
                        .collect()
                }
                Fields::Unnamed(fields) => {
                    (0..fields.unnamed.len())
                        .map(|i| format!("field_{}", i))
                        .collect()
                }
                Fields::Unit => vec![],
            };
            format!("Struct {} with fields: {:?}", name, field_names)
        }
        Data::Enum(data_enum) => {
            let variants: Vec<_> = data_enum.variants.iter()
                .map(|v| v.ident.to_string())
                .collect();
            format!("Enum {} with variants: {:?}", name, variants)
        }
        Data::Union(_) => format!("Union {}", name),
    };

    let gen = quote! {
        impl Describe for #name {
            fn describe() -> &'static str {
                #description
            }
        }
    };

    gen.into()
}

// Usage in main crate:
// use my_macros::HelloMacro;
//
// trait HelloMacro {
//     fn hello_macro();
// }
//
// #[derive(HelloMacro)]
// struct Pancakes;
//
// fn main() {
//     Pancakes::hello_macro();  // "Hello, Macro! My name is Pancakes!"
// }
Derive Macros with Attributes
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Lit, Meta, NestedMeta};

// Derive with helper attributes
#[proc_macro_derive(Builder, attributes(builder))]
pub fn builder_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;
    let builder_name = syn::Ident::new(
        &format!("{}Builder", name),
        name.span()
    );

    // Extract fields and their attributes
    let fields = if let Data::Struct(data) = &ast.data {
        if let Fields::Named(fields) = &data.fields {
            &fields.named
        } else {
            panic!("Builder only works on structs with named fields");
        }
    } else {
        panic!("Builder only works on structs");
    };

    let field_names: Vec<_> = fields.iter()
        .map(|f| f.ident.as_ref().unwrap())
        .collect();

    let field_types: Vec<_> = fields.iter()
        .map(|f| &f.ty)
        .collect();

    let gen = quote! {
        #[derive(Default)]
        pub struct #builder_name {
            #( #field_names: Option<#field_types>, )*
        }

        impl #builder_name {
            pub fn new() -> Self {
                Self::default()
            }

            #(
                pub fn #field_names(mut self, value: #field_types) -> Self {
                    self.#field_names = Some(value);
                    self
                }
            )*

            pub fn build(self) -> Result<#name, &'static str> {
                Ok(#name {
                    #(
                        #field_names: self.#field_names
                            .ok_or(concat!("Missing field: ", stringify!(#field_names)))?,
                    )*
                })
            }
        }

        impl #name {
            pub fn builder() -> #builder_name {
                #builder_name::new()
            }
        }
    };

    gen.into()
}

// Usage:
// #[derive(Builder)]
// struct Command {
//     executable: String,
//     #[builder(default)]
//     args: Vec<String>,
// }
Attribute Macros
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, AttributeArgs};

// Attribute macro for timing functions
#[proc_macro_attribute]
pub fn timed(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);

    let fn_name = &input.sig.ident;
    let fn_block = &input.block;
    let fn_vis = &input.vis;
    let fn_sig = &input.sig;

    let gen = quote! {
        #fn_vis #fn_sig {
            let start = std::time::Instant::now();
            let result = (|| #fn_block)();
            let duration = start.elapsed();
            println!("{} took {:?}", stringify!(#fn_name), duration);
            result
        }
    };

    gen.into()
}

// Attribute macro with arguments
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    let args = parse_macro_input!(attr as AttributeArgs);
    let input = parse_macro_input!(item as ItemFn);

    // Parse route path from attributes
    let path = if let Some(NestedMeta::Lit(Lit::Str(s))) = args.first() {
        s.value()
    } else {
        "/".to_string()
    };

    let fn_name = &input.sig.ident;
    let fn_vis = &input.vis;
    let fn_sig = &input.sig;
    let fn_block = &input.block;

    let gen = quote! {
        #fn_vis #fn_sig #fn_block

        inventory::submit! {
            Route {
                path: #path,
                handler: #fn_name,
            }
        }
    };

    gen.into()
}

// Usage:
// #[timed]
// fn slow_function() {
//     std::thread::sleep(Duration::from_secs(1));
// }
//
// #[route("/api/users")]
// fn get_users() -> Vec<User> { ... }
Function-like Procedural Macros
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr, parse::Parse, parse::ParseStream, Token};

// SQL query macro with compile-time validation
struct SqlQuery {
    query: String,
}

impl Parse for SqlQuery {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let lit: LitStr = input.parse()?;
        Ok(SqlQuery { query: lit.value() })
    }
}

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let SqlQuery { query } = parse_macro_input!(input as SqlQuery);

    // Could validate SQL at compile time here
    if !query.to_uppercase().starts_with("SELECT") {
        return syn::Error::new(
            proc_macro2::Span::call_site(),
            "Only SELECT queries are supported"
        ).to_compile_error().into();
    }

    let gen = quote! {
        Query::new(#query)
    };

    gen.into()
}

// Custom DSL macro
struct KeyValue {
    key: syn::Ident,
    value: syn::Expr,
}

impl Parse for KeyValue {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let key: syn::Ident = input.parse()?;
        input.parse::<Token![=>]>()?;
        let value: syn::Expr = input.parse()?;
        Ok(KeyValue { key, value })
    }
}

struct Config {
    items: Vec<KeyValue>,
}

impl Parse for Config {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let items = input.parse_terminated::<KeyValue, Token![,]>(KeyValue::parse)?;
        Ok(Config { items: items.into_iter().collect() })
    }
}

#[proc_macro]
pub fn config(input: TokenStream) -> TokenStream {
    let Config { items } = parse_macro_input!(input as Config);

    let keys: Vec<_> = items.iter().map(|kv| &kv.key).collect();
    let values: Vec<_> = items.iter().map(|kv| &kv.value).collect();

    let gen = quote! {
        {
            let mut config = Config::new();
            #(
                config.set(stringify!(#keys), #values);
            )*
            config
        }
    };

    gen.into()
}

// Usage:
// let query = sql!("SELECT * FROM users WHERE id = ?");
// let cfg = config!(timeout => 30, retries => 3);

Macro Best Practices

// 1. Use descriptive names
macro_rules! assert_approx_eq {
    ($left:expr, $right:expr, $epsilon:expr) => {
        let left_val = $left;
        let right_val = $right;
        if (left_val - right_val).abs() > $epsilon {
            panic!(
                "assertion failed: {} ≈ {} (difference: {}, epsilon: {})",
                left_val, right_val,
                (left_val - right_val).abs(),
                $epsilon
            );
        }
    };
}

// 2. Use helper macros for complex logic
macro_rules! impl_from_for_error {
    ($error_type:ty, $variant:ident, $source_type:ty) => {
        impl From<$source_type> for $error_type {
            fn from(err: $source_type) -> Self {
                Self::$variant(err)
            }
        }
    };
}

// 3. Document macros well
/// Creates a new HashMap with the given key-value pairs.
///
/// # Examples
/// ```
/// let map = hashmap! {
///     "key1" => "value1",
///     "key2" => "value2",
/// };
/// ```
macro_rules! hashmap {
    // ... implementation
    () => { std::collections::HashMap::new() };
}

// 4. Export macros properly
#[macro_export]
macro_rules! my_public_macro {
    () => { /* ... */ };
}

// 5. Use $crate for path hygiene
#[macro_export]
macro_rules! create_struct {
    ($name:ident) => {
        struct $name {
            data: $crate::internal::Data,
        }
    };
}

7. Unsafe Rust

Unsafe Rust provides escape hatches for when the compiler's safety guarantees are too restrictive. It doesn't disable the borrow checker—it enables five specific capabilities that the compiler can't verify. The goal of unsafe is not to write dangerous code, but to write safe abstractions that require low-level operations internally.

The Five Unsafe Superpowers:

  1. Dereference raw pointers - *const T and *mut T
  2. Call unsafe functions or methods
  3. Access or modify mutable static variables
  4. Implement unsafe traits
  5. Access union fields

Philosophy:

  • unsafe doesn't mean "dangerous"—it means "the programmer guarantees safety"
  • Keep unsafe blocks as small as possible
  • Document safety invariants with // SAFETY: comments
  • Encapsulate unsafe code in safe abstractions
  • Prefer safe alternatives when they exist

Raw Pointers

Raw pointers (*const T and *mut T) bypass Rust's borrowing rules:

fn raw_pointer_basics() {
    let mut num = 5;

    // Creating raw pointers is safe
    let r1: *const i32 = &num as *const i32;
    let r2: *mut i32 = &mut num as *mut i32;

    // Dereferencing raw pointers requires unsafe
    unsafe {
        println!("r1 points to: {}", *r1);
        *r2 = 10;
        println!("r2 now points to: {}", *r2);
    }

    // Raw pointers can be null
    let null_ptr: *const i32 = std::ptr::null();

    // Raw pointers can point to invalid memory
    let arbitrary_address = 0x012345usize;
    let _invalid_ptr = arbitrary_address as *const i32;
    // DON'T dereference this!

    // Pointer arithmetic
    let arr = [1, 2, 3, 4, 5];
    let ptr = arr.as_ptr();

    unsafe {
        for i in 0..5 {
            // offset() for pointer arithmetic
            println!("arr[{}] = {}", i, *ptr.add(i));
        }
    }
}

// Safe abstraction over raw pointers: split_at_mut
fn split_at_mut_example() {
    let mut v = vec![1, 2, 3, 4, 5, 6];
    let (left, right) = v.split_at_mut(3);
    // left and right are both &mut [i32], which Rust normally wouldn't allow
    left[0] = 10;
    right[0] = 20;
    println!("{:?}, {:?}", left, right);  // [10, 2, 3], [20, 5, 6]
}

// Implementing split_at_mut ourselves
fn my_split_at_mut<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();

    assert!(mid <= len);

    // SAFETY: We've verified mid <= len, so:
    // - Both slices are within the original allocation
    // - The slices don't overlap
    // - We're returning two mutable references to non-overlapping parts
    unsafe {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

// Working with C strings
fn c_string_example() {
    use std::ffi::{CStr, CString};

    // Create C string from Rust
    let rust_string = "Hello, C world!";
    let c_string = CString::new(rust_string).unwrap();
    let ptr: *const i8 = c_string.as_ptr();

    // Convert C string back to Rust
    unsafe {
        let c_str = CStr::from_ptr(ptr);
        let rust_str = c_str.to_str().unwrap();
        println!("{}", rust_str);
    }
}

Unsafe Functions and Methods

// Declaring an unsafe function
unsafe fn dangerous_operation(ptr: *mut i32) {
    // SAFETY: Caller must ensure ptr is valid and properly aligned
    *ptr += 1;
}

fn calling_unsafe_functions() {
    let mut num = 5;
    let ptr = &mut num as *mut i32;

    // Must use unsafe block to call
    unsafe {
        dangerous_operation(ptr);
    }

    println!("num is now: {}", num);
}

// Safe wrapper around unsafe code
pub fn safe_increment(value: &mut i32) {
    let ptr = value as *mut i32;
    // SAFETY: ptr is derived from a valid mutable reference
    unsafe {
        dangerous_operation(ptr);
    }
}

// Unsafe traits
unsafe trait UnsafeTrait {
    fn dangerous_method(&self);
}

// SAFETY: Implementing type must uphold the trait's invariants
unsafe impl UnsafeTrait for i32 {
    fn dangerous_method(&self) {
        println!("Value: {}", self);
    }
}

// Common unsafe trait: Send and Sync
struct MySendType {
    ptr: *mut i32,
}

// SAFETY: We ensure the pointee is only accessed from one thread at a time
unsafe impl Send for MySendType {}

// SAFETY: We ensure all access is synchronized
unsafe impl Sync for MySendType {}

Mutable Static Variables

// Static variables have 'static lifetime
static HELLO_WORLD: &str = "Hello, world!";

// Mutable statics require unsafe to access
static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    // SAFETY: We're single-threaded, so no data races
    unsafe {
        COUNTER += inc;
    }
}

fn get_count() -> u32 {
    // SAFETY: We're single-threaded, so no data races
    unsafe { COUNTER }
}

// Better alternative: use atomic types
use std::sync::atomic::{AtomicU32, Ordering};

static SAFE_COUNTER: AtomicU32 = AtomicU32::new(0);

fn safe_add_to_count(inc: u32) {
    SAFE_COUNTER.fetch_add(inc, Ordering::SeqCst);
}

fn safe_get_count() -> u32 {
    SAFE_COUNTER.load(Ordering::SeqCst)
}

// Or use OnceCell/LazyLock for complex initialization
use std::sync::OnceLock;

static CONFIG: OnceLock<Config> = OnceLock::new();

fn get_config() -> &'static Config {
    CONFIG.get_or_init(|| {
        Config::load_from_file("config.toml").unwrap()
    })
}

struct Config;
impl Config {
    fn load_from_file(_path: &str) -> Result<Self, ()> { Ok(Config) }
}

Unions

Unions are like enums but all variants share the same memory:

// Union for type punning
#[repr(C)]
union FloatBits {
    f: f32,
    bits: u32,
}

fn examine_float_bits() {
    let value = FloatBits { f: 1.0 };

    // SAFETY: Reading any union field is valid (though bits may be garbage)
    // Here we know f32 and u32 have the same size
    unsafe {
        println!("1.0f32 as bits: 0x{:08x}", value.bits);
        // Output: 0x3f800000
    }
}

// Unions for FFI compatibility
#[repr(C)]
union IpAddress {
    v4: [u8; 4],
    v6: [u16; 8],
}

// Using ManuallyDrop in unions for non-Copy types
use std::mem::ManuallyDrop;

union MaybeString {
    nothing: (),
    string: ManuallyDrop<String>,
}

impl MaybeString {
    fn new_string(s: String) -> Self {
        MaybeString {
            string: ManuallyDrop::new(s)
        }
    }

    // SAFETY: Only call if string variant is active
    unsafe fn get_string(&self) -> &String {
        &self.string
    }

    // SAFETY: Only call once, and only if string variant is active
    unsafe fn take_string(&mut self) -> String {
        ManuallyDrop::take(&mut self.string)
    }
}

FFI (Foreign Function Interface)

Calling C code from Rust and exposing Rust code to C:

// Calling C functions
extern "C" {
    fn abs(input: i32) -> i32;
    fn strlen(s: *const i8) -> usize;
    fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8;
}

fn call_c_functions() {
    unsafe {
        println!("Absolute value of -3: {}", abs(-3));

        let c_string = b"Hello\0";
        let len = strlen(c_string.as_ptr() as *const i8);
        println!("String length: {}", len);
    }
}

// Exposing Rust functions to C
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}

#[no_mangle]
pub extern "C" fn rust_multiply(a: f64, b: f64) -> f64 {
    a * b
}

// Callbacks from C
type CCallback = extern "C" fn(i32) -> i32;

extern "C" {
    fn register_callback(cb: CCallback);
}

extern "C" fn my_callback(value: i32) -> i32 {
    println!("Callback received: {}", value);
    value * 2
}

fn register_with_c() {
    unsafe {
        register_callback(my_callback);
    }
}

// Working with C structs
#[repr(C)]
struct Point {
    x: f64,
    y: f64,
}

extern "C" {
    fn distance(p1: *const Point, p2: *const Point) -> f64;
}

fn use_c_struct() {
    let p1 = Point { x: 0.0, y: 0.0 };
    let p2 = Point { x: 3.0, y: 4.0 };

    unsafe {
        let dist = distance(&p1, &p2);
        println!("Distance: {}", dist);  // 5.0
    }
}

// Linking with libraries
// In Cargo.toml or build.rs:
// #[link(name = "mylib")]
// #[link(name = "mylib", kind = "static")]
// #[link(name = "mylib", kind = "framework")]  // macOS

extern "C" {
    // Functions from linked library
}

Inline Assembly

use std::arch::asm;

fn inline_assembly_examples() {
    // Basic inline assembly (x86_64)
    let result: u64;
    unsafe {
        asm!(
            "mov {}, 42",
            out(reg) result,
        );
    }
    println!("Result: {}", result);

    // Computation with inputs and outputs
    let x: u64 = 10;
    let y: u64 = 20;
    let sum: u64;
    unsafe {
        asm!(
            "add {0}, {1}",
            inout(reg) x => sum,
            in(reg) y,
        );
    }
    println!("Sum: {}", sum);

    // CPUID example
    let (eax, ebx, ecx, edx): (u32, u32, u32, u32);
    unsafe {
        asm!(
            "cpuid",
            inout("eax") 0 => eax,
            out("ebx") ebx,
            out("ecx") ecx,
            out("edx") edx,
        );
    }
    println!("CPUID: {} {} {} {}", eax, ebx, ecx, edx);
}

// Platform-specific intrinsics
#[cfg(target_arch = "x86_64")]
fn use_simd() {
    use std::arch::x86_64::*;

    unsafe {
        let a = _mm256_set_ps(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0);
        let b = _mm256_set_ps(8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0);
        let result = _mm256_add_ps(a, b);
        // All elements are 9.0
    }
}

Unsafe Best Practices

// 1. Document safety requirements
/// Dereferences a raw pointer to read a value.
///
/// # Safety
///
/// - `ptr` must be valid for reads
/// - `ptr` must be properly aligned
/// - `ptr` must point to a properly initialized value
unsafe fn read_ptr<T: Copy>(ptr: *const T) -> T {
    *ptr
}

// 2. Minimize unsafe scope
fn process_data(data: &mut [u32]) {
    // Safe code here...

    // SAFETY: We've verified the slice has at least one element above
    let first = unsafe { data.get_unchecked(0) };

    // Safe code continues...
}

// 3. Create safe abstractions
pub struct SafeWrapper {
    ptr: *mut i32,
}

impl SafeWrapper {
    pub fn new(value: i32) -> Self {
        let ptr = Box::into_raw(Box::new(value));
        SafeWrapper { ptr }
    }

    pub fn get(&self) -> i32 {
        // SAFETY: ptr is always valid because we created it from Box
        // and only free it in Drop
        unsafe { *self.ptr }
    }

    pub fn set(&mut self, value: i32) {
        // SAFETY: same as get()
        unsafe { *self.ptr = value }
    }
}

impl Drop for SafeWrapper {
    fn drop(&mut self) {
        // SAFETY: ptr was created by Box::into_raw, we're only calling
        // this once (in Drop), and we never expose the raw pointer
        unsafe { drop(Box::from_raw(self.ptr)) }
    }
}

// 4. Use #[deny(unsafe_op_in_unsafe_fn)] for better hygiene
#![deny(unsafe_op_in_unsafe_fn)]

unsafe fn strict_unsafe_function(ptr: *const i32) -> i32 {
    // Even inside unsafe fn, must use unsafe block
    // SAFETY: Caller guarantees ptr is valid
    unsafe { *ptr }
}

8. Async/Await

Rust's async/await provides efficient asynchronous programming using cooperative multitasking. Unlike OS threads, async tasks are lightweight and can number in the millions. The async model uses zero-cost abstractions—async code compiles to state machines with no hidden allocations.

Key Concepts:

Concept Description
async fn Declares a function returning a Future
.await Suspends execution until a Future completes
Future Trait representing an asynchronous computation
Executor/Runtime Polls futures to completion (Tokio, async-std)
Pin Ensures self-referential futures aren't moved
Stream Async version of Iterator

When to Use Async:

  • I/O-bound work: Network requests, file operations, database queries
  • High concurrency: Thousands of simultaneous connections
  • Event-driven systems: Web servers, message processors

When NOT to Use Async:

  • CPU-bound work: Use threads or rayon instead
  • Simple scripts: Async adds complexity
  • Synchronous dependencies: Blocking calls negate async benefits

Understanding Futures

A Future is a value that might not be ready yet:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

// Futures are state machines
// async fn becomes something like this:
enum ReadFileFuture {
    Opening,
    Reading { file: std::fs::File },
    Done,
}

// Manual Future implementation
struct CountdownFuture {
    count: u32,
}

impl Future for CountdownFuture {
    type Output = String;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.count == 0 {
            Poll::Ready("Liftoff!".to_string())
        } else {
            self.count -= 1;
            // Tell the runtime to poll us again
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

// async fn desugars to returning impl Future
async fn async_hello() -> String {
    "Hello, async!".to_string()
}

// Equivalent to:
fn sync_hello() -> impl Future<Output = String> {
    async { "Hello, async!".to_string() }
}

// Futures are lazy - nothing happens until polled
fn futures_are_lazy() {
    let future = async_hello();  // Nothing printed yet!
    // Must await or spawn to execute
}

Basic Async/Await

// async fn returns impl Future<Output = T>
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
    // .await suspends until the request completes
    let response = reqwest::get(url).await?;
    let body = response.text().await?;
    Ok(body)
}

async fn process_urls(urls: Vec<&str>) {
    for url in urls {
        match fetch_data(url).await {
            Ok(data) => println!("Got {} bytes from {}", data.len(), url),
            Err(e) => eprintln!("Error fetching {}: {}", url, e),
        }
    }
}

// Async blocks for inline futures
async fn async_blocks() {
    let future = async {
        // Async code here
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        42
    };

    let result = future.await;
    println!("Result: {}", result);
}

// move async blocks capture by value
fn move_async_block() -> impl Future<Output = String> {
    let s = String::from("hello");

    async move {
        // s is moved into this future
        format!("{} world", s)
    }
}

Tokio Runtime

Tokio is the most popular async runtime for Rust:

// Cargo.toml: tokio = { version = "1", features = ["full"] }

use tokio::time::{sleep, Duration, timeout};
use tokio::task;

// Using #[tokio::main] macro
#[tokio::main]
async fn main() {
    println!("Starting async application");

    // Simple async operation
    sleep(Duration::from_millis(100)).await;

    // Run multiple tasks concurrently
    let result = run_concurrent_tasks().await;
    println!("Concurrent result: {:?}", result);
}

// Manual runtime creation
fn manual_runtime() {
    let rt = tokio::runtime::Runtime::new().unwrap();

    rt.block_on(async {
        println!("Running in manually created runtime");
    });

    // Or spawn and don't wait
    rt.spawn(async {
        println!("Background task");
    });
}

// Spawning tasks
async fn run_concurrent_tasks() -> Vec<i32> {
    let mut handles = vec![];

    for i in 0..5 {
        // spawn returns JoinHandle
        let handle = task::spawn(async move {
            sleep(Duration::from_millis(100 * i as u64)).await;
            i * 2
        });
        handles.push(handle);
    }

    // Wait for all tasks
    let mut results = vec![];
    for handle in handles {
        results.push(handle.await.unwrap());
    }
    results
}

// Timeouts
async fn with_timeout() {
    let slow_future = async {
        sleep(Duration::from_secs(10)).await;
        "completed"
    };

    match timeout(Duration::from_secs(1), slow_future).await {
        Ok(result) => println!("Got result: {}", result),
        Err(_) => println!("Operation timed out"),
    }
}

// Blocking operations in async context
async fn handle_blocking() {
    // For CPU-bound work or blocking I/O
    let result = task::spawn_blocking(|| {
        // This runs on a separate thread pool
        std::thread::sleep(std::time::Duration::from_secs(1));
        "blocking completed"
    }).await.unwrap();

    println!("Blocking result: {}", result);
}

// Yielding to other tasks
async fn cooperative() {
    for i in 0..1000 {
        // CPU-intensive work
        expensive_computation(i);

        // Yield periodically to not starve other tasks
        if i % 100 == 0 {
            task::yield_now().await;
        }
    }
}

fn expensive_computation(_: i32) {}

Concurrent Operations with join! and select!

use tokio::{join, select, time::{sleep, Duration}};

async fn fetch_user(id: u64) -> String {
    sleep(Duration::from_millis(100)).await;
    format!("User {}", id)
}

async fn fetch_posts(user_id: u64) -> Vec<String> {
    sleep(Duration::from_millis(150)).await;
    vec![format!("Post by {}", user_id)]
}

// join! - wait for all futures concurrently
async fn parallel_fetch() {
    let (user, posts) = join!(
        fetch_user(1),
        fetch_posts(1)
    );
    println!("User: {}, Posts: {:?}", user, posts);
}

// try_join! - short-circuit on first error
async fn parallel_with_errors() -> Result<(), Box<dyn std::error::Error>> {
    async fn fallible_op() -> Result<String, &'static str> {
        Ok("success".to_string())
    }

    let (a, b) = tokio::try_join!(
        fallible_op(),
        fallible_op()
    )?;

    println!("Both succeeded: {}, {}", a, b);
    Ok(())
}

// select! - wait for first future to complete
async fn race_operations() {
    select! {
        _ = sleep(Duration::from_secs(1)) => {
            println!("Timeout occurred");
        }
        result = fetch_user(1) => {
            println!("Got user: {}", result);
        }
    }
}

// select! with loop for event handling
async fn event_loop() {
    let mut interval = tokio::time::interval(Duration::from_secs(1));
    let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(10);

    // Simulate sending a message
    let tx_clone = tx.clone();
    tokio::spawn(async move {
        sleep(Duration::from_millis(500)).await;
        tx_clone.send("Hello".to_string()).await.ok();
    });

    loop {
        select! {
            _ = interval.tick() => {
                println!("Tick");
            }
            Some(msg) = rx.recv() => {
                println!("Received: {}", msg);
                break;
            }
        }
    }
}

// Biased select! for priority
async fn priority_select() {
    let (tx1, mut rx1) = tokio::sync::mpsc::channel::<i32>(10);
    let (tx2, mut rx2) = tokio::sync::mpsc::channel::<i32>(10);

    // biased; gives priority to earlier branches
    select! {
        biased;

        Some(high_priority) = rx1.recv() => {
            println!("High priority: {}", high_priority);
        }
        Some(low_priority) = rx2.recv() => {
            println!("Low priority: {}", low_priority);
        }
    }
}

Async Channels

use tokio::sync::{mpsc, oneshot, broadcast, watch};

// mpsc: Multi-producer, single-consumer
async fn mpsc_example() {
    let (tx, mut rx) = mpsc::channel::<String>(32);

    // Multiple senders
    for i in 0..3 {
        let tx = tx.clone();
        tokio::spawn(async move {
            tx.send(format!("Message from {}", i)).await.unwrap();
        });
    }
    drop(tx);  // Drop original sender

    // Single receiver
    while let Some(msg) = rx.recv().await {
        println!("Received: {}", msg);
    }
}

// oneshot: Single value, single use
async fn oneshot_example() {
    let (tx, rx) = oneshot::channel::<String>();

    tokio::spawn(async move {
        // Simulate async work
        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
        tx.send("Result".to_string()).unwrap();
    });

    match rx.await {
        Ok(result) => println!("Got: {}", result),
        Err(_) => println!("Sender dropped"),
    }
}

// broadcast: Multiple consumers, each gets all messages
async fn broadcast_example() {
    let (tx, _rx) = broadcast::channel::<String>(16);

    let mut rx1 = tx.subscribe();
    let mut rx2 = tx.subscribe();

    tokio::spawn(async move {
        while let Ok(msg) = rx1.recv().await {
            println!("Receiver 1: {}", msg);
        }
    });

    tokio::spawn(async move {
        while let Ok(msg) = rx2.recv().await {
            println!("Receiver 2: {}", msg);
        }
    });

    tx.send("Hello everyone".to_string()).unwrap();
}

// watch: Single value that can be observed
async fn watch_example() {
    let (tx, mut rx) = watch::channel("initial".to_string());

    tokio::spawn(async move {
        loop {
            rx.changed().await.unwrap();
            println!("Value changed to: {}", *rx.borrow());
        }
    });

    tx.send("updated".to_string()).unwrap();
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}

Streams (Async Iterators)

use tokio_stream::{self as stream, StreamExt};

// Basic stream usage
async fn stream_basics() {
    let mut stream = stream::iter(vec![1, 2, 3, 4, 5]);

    while let Some(value) = stream.next().await {
        println!("Value: {}", value);
    }
}

// Stream combinators
async fn stream_combinators() {
    let stream = stream::iter(1..=10)
        .filter(|x| futures::future::ready(x % 2 == 0))
        .map(|x| x * 2)
        .take(3);

    tokio::pin!(stream);

    while let Some(value) = stream.next().await {
        println!("Value: {}", value);
    }
}

// Creating streams from channels
async fn channel_as_stream() {
    let (tx, rx) = tokio::sync::mpsc::channel::<i32>(10);

    tokio::spawn(async move {
        for i in 0..5 {
            tx.send(i).await.unwrap();
        }
    });

    // ReceiverStream adapter
    use tokio_stream::wrappers::ReceiverStream;
    let mut stream = ReceiverStream::new(rx);

    while let Some(value) = stream.next().await {
        println!("From channel: {}", value);
    }
}

// Interval stream
async fn interval_stream() {
    use tokio_stream::wrappers::IntervalStream;
    use tokio::time::{interval, Duration};

    let mut stream = IntervalStream::new(interval(Duration::from_millis(100)))
        .take(5);

    while let Some(_instant) = stream.next().await {
        println!("Tick");
    }
}

Async Traits

// Rust 1.75+: Native async trait methods
trait AsyncDatabase {
    async fn get(&self, key: &str) -> Option<String>;
    async fn set(&self, key: &str, value: &str) -> Result<(), String>;
}

struct InMemoryDb {
    data: std::collections::HashMap<String, String>,
}

impl AsyncDatabase for InMemoryDb {
    async fn get(&self, key: &str) -> Option<String> {
        self.data.get(key).cloned()
    }

    async fn set(&self, _key: &str, _value: &str) -> Result<(), String> {
        // Would need interior mutability for real implementation
        Ok(())
    }
}

// For trait objects, use #[trait_variant::make(SendDatabase: Send)]
// or the async-trait crate for backward compatibility

// Using async-trait crate (for older Rust or Send bounds)
// use async_trait::async_trait;
//
// #[async_trait]
// trait AsyncTrait {
//     async fn async_method(&self) -> String;
// }

// Boxed futures for trait objects
trait DynAsyncTrait {
    fn async_method(&self) -> std::pin::Pin<Box<dyn std::future::Future<Output = String> + Send + '_>>;
}

Cancellation and Graceful Shutdown

use tokio::signal;
use tokio::sync::watch;

async fn graceful_shutdown() {
    let (shutdown_tx, mut shutdown_rx) = watch::channel(false);

    // Spawn worker tasks
    let worker = tokio::spawn(async move {
        loop {
            tokio::select! {
                _ = shutdown_rx.changed() => {
                    if *shutdown_rx.borrow() {
                        println!("Worker shutting down");
                        break;
                    }
                }
                _ = do_work() => {
                    println!("Work completed");
                }
            }
        }
    });

    // Wait for shutdown signal
    signal::ctrl_c().await.unwrap();
    println!("Shutdown signal received");

    // Notify workers
    shutdown_tx.send(true).unwrap();

    // Wait for workers to finish
    worker.await.unwrap();
    println!("Graceful shutdown complete");
}

async fn do_work() {
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}

// CancellationToken for structured cancellation
use tokio_util::sync::CancellationToken;

async fn with_cancellation_token() {
    let token = CancellationToken::new();

    let task_token = token.clone();
    let task = tokio::spawn(async move {
        tokio::select! {
            _ = task_token.cancelled() => {
                println!("Task was cancelled");
            }
            _ = long_running_operation() => {
                println!("Task completed");
            }
        }
    });

    // Cancel after 1 second
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    token.cancel();

    task.await.unwrap();
}

async fn long_running_operation() {
    tokio::time::sleep(std::time::Duration::from_secs(10)).await;
}

9. Iterators

Iterators are Rust's primary abstraction for processing sequences. They are lazy (compute values on demand), composable (chain operations together), and zero-cost (compile to efficient loops). The iterator pattern is pervasive in Rust—most collection operations use iterators.

Core Traits:

Trait Description Key Method
Iterator Produces a sequence of values fn next(&mut self) -> Option<Self::Item>
IntoIterator Can be converted to an iterator fn into_iter(self) -> Self::IntoIter
FromIterator Can be built from an iterator fn from_iter<I: IntoIterator>(iter: I) -> Self
DoubleEndedIterator Can iterate from both ends fn next_back(&mut self) -> Option<Self::Item>
ExactSizeIterator Knows its exact length fn len(&self) -> usize

Iterator Types for Collections:

Method Yields Ownership
.iter() &T Borrows collection
.iter_mut() &mut T Borrows mutably
.into_iter() T Consumes collection

Basic Iterator Usage

fn basic_iteration() {
    let numbers = vec![1, 2, 3, 4, 5];

    // for loop uses IntoIterator
    for n in &numbers {  // Same as numbers.iter()
        println!("{}", n);
    }

    // Explicit iterator
    let mut iter = numbers.iter();
    while let Some(n) = iter.next() {
        println!("{}", n);
    }

    // Different iterator types
    let v = vec![String::from("a"), String::from("b")];

    for s in &v {           // s: &String (borrowing)
        println!("{}", s);
    }

    for s in &mut v.clone() {  // s: &mut String (mutable borrow)
        s.push_str("!");
    }

    for s in v {            // s: String (ownership transfer)
        println!("{}", s);
    }
    // v is no longer accessible
}

// Ranges are iterators
fn range_iterators() {
    // Exclusive range
    for i in 0..5 {  // 0, 1, 2, 3, 4
        print!("{} ", i);
    }

    // Inclusive range
    for i in 0..=5 {  // 0, 1, 2, 3, 4, 5
        print!("{} ", i);
    }

    // Reverse iteration
    for i in (0..5).rev() {  // 4, 3, 2, 1, 0
        print!("{} ", i);
    }

    // Step by
    for i in (0..10).step_by(2) {  // 0, 2, 4, 6, 8
        print!("{} ", i);
    }
}

Iterator Adapters (Lazy Transformations)

Adapters transform iterators into new iterators without consuming them:

fn iterator_adapters() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // map: Transform each element
    let doubled: Vec<_> = numbers.iter()
        .map(|x| x * 2)
        .collect();
    // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

    // filter: Keep elements matching predicate
    let evens: Vec<_> = numbers.iter()
        .filter(|x| *x % 2 == 0)
        .collect();
    // [2, 4, 6, 8, 10]

    // filter_map: Combined filter and map
    let parsed: Vec<i32> = ["1", "two", "3", "four", "5"]
        .iter()
        .filter_map(|s| s.parse().ok())
        .collect();
    // [1, 3, 5]

    // flat_map: Map then flatten
    let nested = vec![vec![1, 2], vec![3, 4], vec![5]];
    let flat: Vec<_> = nested.iter()
        .flat_map(|v| v.iter())
        .collect();
    // [1, 2, 3, 4, 5]

    // flatten: Flatten nested iterators
    let flat2: Vec<_> = nested.into_iter().flatten().collect();
    // [1, 2, 3, 4, 5]

    // take: Take first n elements
    let first_three: Vec<_> = numbers.iter().take(3).collect();
    // [1, 2, 3]

    // skip: Skip first n elements
    let after_three: Vec<_> = numbers.iter().skip(3).collect();
    // [4, 5, 6, 7, 8, 9, 10]

    // take_while: Take while predicate is true
    let less_than_5: Vec<_> = numbers.iter()
        .take_while(|x| **x < 5)
        .collect();
    // [1, 2, 3, 4]

    // skip_while: Skip while predicate is true
    let from_5: Vec<_> = numbers.iter()
        .skip_while(|x| **x < 5)
        .collect();
    // [5, 6, 7, 8, 9, 10]

    // chain: Concatenate iterators
    let combined: Vec<_> = [1, 2].iter()
        .chain([3, 4].iter())
        .collect();
    // [1, 2, 3, 4]

    // zip: Pair elements from two iterators
    let pairs: Vec<_> = [1, 2, 3].iter()
        .zip(['a', 'b', 'c'].iter())
        .collect();
    // [(1, 'a'), (2, 'b'), (3, 'c')]

    // enumerate: Add indices
    for (index, value) in numbers.iter().enumerate() {
        println!("{}: {}", index, value);
    }

    // peekable: Look at next without consuming
    let mut iter = numbers.iter().peekable();
    while let Some(&value) = iter.next() {
        if let Some(&&next) = iter.peek() {
            println!("{} followed by {}", value, next);
        }
    }

    // cycle: Repeat infinitely
    let repeated: Vec<_> = [1, 2, 3].iter()
        .cycle()
        .take(7)
        .collect();
    // [1, 2, 3, 1, 2, 3, 1]

    // inspect: Debug without consuming
    let result: Vec<_> = numbers.iter()
        .inspect(|x| println!("Before filter: {}", x))
        .filter(|x| *x % 2 == 0)
        .inspect(|x| println!("After filter: {}", x))
        .collect();
}

Consuming Adapters (Terminal Operations)

Consuming adapters evaluate the iterator and produce a final result:

fn consuming_adapters() {
    let numbers = vec![1, 2, 3, 4, 5];

    // collect: Build a collection
    let vec: Vec<i32> = (0..5).collect();
    let set: std::collections::HashSet<i32> = (0..5).collect();
    let string: String = ['h', 'e', 'l', 'l', 'o'].iter().collect();

    // sum and product
    let sum: i32 = numbers.iter().sum();  // 15
    let product: i32 = numbers.iter().product();  // 120

    // count
    let count = numbers.iter().count();  // 5

    // fold: Reduce to single value with accumulator
    let sum_fold = numbers.iter()
        .fold(0, |acc, x| acc + x);

    let sentence = ["hello", "world", "!"].iter()
        .fold(String::new(), |acc, s| {
            if acc.is_empty() {
                s.to_string()
            } else {
                format!("{} {}", acc, s)
            }
        });

    // reduce: Like fold, but uses first element as initial
    let max = numbers.iter()
        .copied()
        .reduce(|a, b| if a > b { a } else { b });

    // find: First element matching predicate
    let first_even = numbers.iter().find(|x| *x % 2 == 0);  // Some(&2)

    // find_map: Combined find and map
    let parsed = ["1", "two", "3"].iter()
        .find_map(|s| s.parse::<i32>().ok());  // Some(1)

    // position: Index of first match
    let pos = numbers.iter().position(|x| *x == 3);  // Some(2)

    // any and all
    let has_even = numbers.iter().any(|x| x % 2 == 0);  // true
    let all_positive = numbers.iter().all(|x| *x > 0);  // true

    // min and max
    let min = numbers.iter().min();  // Some(&1)
    let max = numbers.iter().max();  // Some(&5)

    // min_by and max_by with custom comparison
    let people = vec![("Alice", 30), ("Bob", 25), ("Charlie", 35)];
    let oldest = people.iter()
        .max_by(|a, b| a.1.cmp(&b.1));  // Some(("Charlie", 35))

    // min_by_key and max_by_key
    let youngest = people.iter()
        .min_by_key(|(_, age)| age);  // Some(("Bob", 25))

    // partition: Split into two collections
    let (evens, odds): (Vec<_>, Vec<_>) = numbers.iter()
        .partition(|x| *x % 2 == 0);

    // unzip: Split pairs into two collections
    let pairs = vec![(1, 'a'), (2, 'b'), (3, 'c')];
    let (nums, chars): (Vec<_>, Vec<_>) = pairs.into_iter().unzip();

    // for_each: Execute side effect
    numbers.iter().for_each(|x| println!("{}", x));

    // nth: Get element at index
    let third = numbers.iter().nth(2);  // Some(&3)

    // last: Get last element
    let last = numbers.iter().last();  // Some(&5)
}

Advanced Iterator Patterns

use std::iter::{self, FromIterator};

fn advanced_patterns() {
    // Collect into Result: stops on first error
    let strings = vec!["1", "2", "three", "4"];
    let parsed: Result<Vec<i32>, _> = strings.iter()
        .map(|s| s.parse::<i32>())
        .collect();
    // Err(ParseIntError { ... }) - stops at "three"

    // Collect successes only, ignoring errors
    let parsed_ok: Vec<i32> = strings.iter()
        .filter_map(|s| s.parse().ok())
        .collect();
    // [1, 2, 4]

    // scan: Like fold but yields intermediate values
    let running_sum: Vec<i32> = [1, 2, 3, 4, 5].iter()
        .scan(0, |state, x| {
            *state += x;
            Some(*state)
        })
        .collect();
    // [1, 3, 6, 10, 15]

    // Creating iterators from functions
    let mut count = 0;
    let counter = iter::from_fn(move || {
        count += 1;
        if count <= 5 { Some(count) } else { None }
    });

    // successors: Generate sequence from initial value
    let powers_of_2: Vec<_> = iter::successors(Some(1u32), |n| n.checked_mul(2))
        .take(10)
        .collect();
    // [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

    // repeat and repeat_with
    let fives: Vec<_> = iter::repeat(5).take(3).collect();  // [5, 5, 5]
    let randoms: Vec<_> = iter::repeat_with(|| rand::random::<u8>())
        .take(5)
        .collect();

    // once: Iterator with single element
    let combined: Vec<_> = iter::once(0)
        .chain(1..5)
        .chain(iter::once(100))
        .collect();
    // [0, 1, 2, 3, 4, 100]

    // empty: Iterator with no elements
    let nothing: Vec<i32> = iter::empty().collect();

    // windows: Overlapping slices
    let data = [1, 2, 3, 4, 5];
    for window in data.windows(3) {
        println!("{:?}", window);  // [1,2,3], [2,3,4], [3,4,5]
    }

    // chunks: Non-overlapping slices
    for chunk in data.chunks(2) {
        println!("{:?}", chunk);  // [1,2], [3,4], [5]
    }

    // array_chunks (nightly) / chunks_exact
    for chunk in data.chunks_exact(2) {
        println!("{:?}", chunk);  // [1,2], [3,4] (skips incomplete)
    }
}

// Parallel iteration with rayon
fn parallel_iteration() {
    use rayon::prelude::*;

    let numbers: Vec<i32> = (0..1000).collect();

    // Parallel map
    let squared: Vec<_> = numbers.par_iter()
        .map(|x| x * x)
        .collect();

    // Parallel filter
    let evens: Vec<_> = numbers.par_iter()
        .filter(|x| *x % 2 == 0)
        .collect();

    // Parallel sum
    let sum: i32 = numbers.par_iter().sum();
}

Implementing Iterator

// Basic iterator implementation
struct Counter {
    count: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Counter {
        Counter { count: 0, max }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < self.max {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }

    // Optional: Provide size hint for optimization
    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = (self.max - self.count) as usize;
        (remaining, Some(remaining))
    }
}

// Implement ExactSizeIterator when size is known
impl ExactSizeIterator for Counter {
    fn len(&self) -> usize {
        (self.max - self.count) as usize
    }
}

// DoubleEndedIterator for reverse iteration
struct Range {
    start: i32,
    end: i32,
}

impl Iterator for Range {
    type Item = i32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.start < self.end {
            let result = self.start;
            self.start += 1;
            Some(result)
        } else {
            None
        }
    }
}

impl DoubleEndedIterator for Range {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.start < self.end {
            self.end -= 1;
            Some(self.end)
        } else {
            None
        }
    }
}

// IntoIterator for custom types
struct MyCollection {
    items: Vec<String>,
}

impl IntoIterator for MyCollection {
    type Item = String;
    type IntoIter = std::vec::IntoIter<String>;

    fn into_iter(self) -> Self::IntoIter {
        self.items.into_iter()
    }
}

// Reference iteration
impl<'a> IntoIterator for &'a MyCollection {
    type Item = &'a String;
    type IntoIter = std::slice::Iter<'a, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.items.iter()
    }
}

// FromIterator for collecting into custom types
impl FromIterator<String> for MyCollection {
    fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
        MyCollection {
            items: iter.into_iter().collect(),
        }
    }
}

fn use_custom_iterators() {
    // Counter usage
    let counter = Counter::new(5);
    let sum: u32 = counter.sum();  // 1 + 2 + 3 + 4 + 5 = 15

    // Can use all iterator methods
    let evens: Vec<_> = Counter::new(10)
        .filter(|x| x % 2 == 0)
        .collect();
    // [2, 4, 6, 8, 10]

    // Double-ended range
    let range = Range { start: 1, end: 6 };
    let reversed: Vec<_> = range.rev().collect();
    // [5, 4, 3, 2, 1]

    // Collect into custom collection
    let collection: MyCollection = vec!["a", "b", "c"]
        .into_iter()
        .map(String::from)
        .collect();
}

10. Closures

Closures are anonymous functions that can capture variables from their enclosing scope. They are one of Rust's most powerful features, enabling functional programming patterns, callbacks, and lazy evaluation. Unlike regular functions, closures can "close over" their environment.

Closure Traits:

Trait Captures By Can Call Use Case
Fn &T (shared reference) Multiple times Read-only access
FnMut &mut T (mutable reference) Multiple times Mutable access
FnOnce T (by value) Once Consumes captured values

Trait Hierarchy: Fn: FnMut: FnOnce (Fn is a subset of FnMut, which is a subset of FnOnce)

Basic Closures

fn basic_closures() {
    // Type inference for parameters and return type
    let add_one = |x| x + 1;
    println!("{}", add_one(5));  // 6

    // Multiple parameters
    let add = |x, y| x + y;
    println!("{}", add(2, 3));  // 5

    // Explicit type annotations
    let multiply: fn(i32, i32) -> i32 = |x, y| x * y;
    let divide = |x: f64, y: f64| -> f64 { x / y };

    // Multi-line closure with block
    let complex = |x: i32| {
        let squared = x * x;
        let cubed = squared * x;
        squared + cubed
    };

    // No parameters
    let greet = || println!("Hello!");
    greet();

    // Closure that returns nothing
    let log = |msg: &str| {
        println!("[LOG] {}", msg);
    };
}

Environment Capture

Closures can capture variables from their enclosing scope:

fn capture_examples() {
    // Capture by reference (Fn)
    let x = 10;
    let print_x = || println!("x = {}", x);
    print_x();
    print_x();  // Can call multiple times
    println!("x is still accessible: {}", x);

    // Capture by mutable reference (FnMut)
    let mut counter = 0;
    let mut increment = || {
        counter += 1;
        counter
    };
    println!("{}", increment());  // 1
    println!("{}", increment());  // 2
    println!("final counter: {}", counter);  // 2

    // Capture by value with move (FnOnce, but could be Fn if no mutation)
    let data = vec![1, 2, 3];
    let consume = move || {
        println!("Taking ownership: {:?}", data);
        data  // Returns owned data
    };
    let _owned = consume();
    // consume();  // Error: already moved
    // data;       // Error: already moved into closure

    // move with Copy types
    let x = 42;  // i32 implements Copy
    let closure = move || x * 2;
    println!("{}", closure());  // Works
    println!("{}", x);  // x is still accessible (copied, not moved)
}

// Why capture matters
fn capture_semantics() {
    let mut s = String::from("hello");

    // This closure captures s by mutable reference
    let mut append = || s.push_str(" world");

    // Can't use s while closure exists and might mutate it
    // println!("{}", s);  // Error: s is borrowed mutably

    append();

    // Now we can use s again
    println!("{}", s);  // "hello world"
}

Closure Trait Inference

The compiler infers which trait a closure implements based on how it uses captured variables:

fn trait_inference() {
    // Fn - only reads captured value
    let x = 5;
    let fn_closure = || println!("{}", x);
    call_fn(&fn_closure);
    call_fn(&fn_closure);  // Fn can be called multiple times

    // FnMut - modifies captured value
    let mut y = 5;
    let mut fn_mut_closure = || y += 1;
    call_fn_mut(&mut fn_mut_closure);
    call_fn_mut(&mut fn_mut_closure);

    // FnOnce - consumes captured value
    let z = String::from("hello");
    let fn_once_closure = || {
        drop(z);  // Consumes z
    };
    call_fn_once(fn_once_closure);
    // call_fn_once(fn_once_closure);  // Error: already consumed
}

fn call_fn<F: Fn()>(f: &F) {
    f();
}

fn call_fn_mut<F: FnMut()>(f: &mut F) {
    f();
}

fn call_fn_once<F: FnOnce()>(f: F) {
    f();
}

// Accepting any callable
fn flexible_callback<F>(f: F)
where
    F: FnOnce(),  // FnOnce accepts Fn, FnMut, and FnOnce
{
    f();
}

Closures as Parameters

// Generic closure parameter
fn apply<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

// Multiple calls require Fn or FnMut
fn apply_twice<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(f(x))
}

// Mutable closure parameter
fn call_with_counter<F>(mut f: F) -> i32
where
    F: FnMut() -> i32,
{
    f() + f()
}

// Using impl Trait (simpler syntax)
fn apply_simple(f: impl Fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

fn use_closure_params() {
    let result = apply(|x| x * 2, 5);  // 10
    let result = apply_twice(|x| x + 1, 5);  // 7

    let mut count = 0;
    let result = call_with_counter(|| {
        count += 1;
        count
    });
    println!("Result: {}, count: {}", result, count);  // 3, 2
}

// Choosing the right trait bound
// Use FnOnce when you only call once
// Use FnMut when you need to call multiple times with mutation
// Use Fn when you need to call multiple times without mutation
// Prefer FnOnce for maximum flexibility (accepts all closure types)

Returning Closures

Closures have anonymous types, so returning them requires special handling:

// Return closure as trait object (dynamic dispatch)
fn returns_closure_boxed() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

// Return closure as impl Trait (static dispatch, more efficient)
fn returns_closure_impl() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

// Closure that captures environment
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

// Closure factory with mutable state
fn make_counter() -> impl FnMut() -> i32 {
    let mut count = 0;
    move || {
        count += 1;
        count
    }
}

// Multiple return paths require Box (or enum)
fn conditional_closure(use_add: bool) -> Box<dyn Fn(i32) -> i32> {
    if use_add {
        Box::new(|x| x + 1)
    } else {
        Box::new(|x| x * 2)
    }
}

fn use_returned_closures() {
    let add_one = returns_closure_impl();
    println!("{}", add_one(5));  // 6

    let add_five = make_adder(5);
    println!("{}", add_five(10));  // 15

    let mut counter = make_counter();
    println!("{}", counter());  // 1
    println!("{}", counter());  // 2
    println!("{}", counter());  // 3
}

Closure and Function Pointer Coercion

// Function pointer type (no environment capture)
fn regular_function(x: i32) -> i32 {
    x + 1
}

fn use_function_pointers() {
    // Function pointer type
    let fn_ptr: fn(i32) -> i32 = regular_function;

    // Non-capturing closures can coerce to function pointers
    let closure_ptr: fn(i32) -> i32 = |x| x + 1;

    // Capturing closures CANNOT be function pointers
    let y = 5;
    // let invalid: fn(i32) -> i32 = |x| x + y;  // Error!

    // Use in FFI or when exact fn signature required
    extern "C" fn c_callback(x: i32) -> i32 { x }

    // Arrays of function pointers
    let operations: [fn(i32) -> i32; 3] = [
        |x| x + 1,
        |x| x * 2,
        |x| x - 1,
    ];

    for op in operations {
        println!("{}", op(5));
    }
}

Advanced Closure Patterns

use std::collections::HashMap;

// Memoization cache
struct Memoizer<T, R>
where
    T: Fn(u64) -> R,
    R: Clone,
{
    calculation: T,
    cache: HashMap<u64, R>,
}

impl<T, R> Memoizer<T, R>
where
    T: Fn(u64) -> R,
    R: Clone,
{
    fn new(calculation: T) -> Self {
        Memoizer {
            calculation,
            cache: HashMap::new(),
        }
    }

    fn value(&mut self, arg: u64) -> R {
        self.cache.get(&arg).cloned().unwrap_or_else(|| {
            let result = (self.calculation)(arg);
            self.cache.insert(arg, result.clone());
            result
        })
    }
}

// Lazy initialization
struct Lazy<T, F: FnOnce() -> T> {
    init: Option<F>,
    value: Option<T>,
}

impl<T, F: FnOnce() -> T> Lazy<T, F> {
    fn new(init: F) -> Self {
        Lazy { init: Some(init), value: None }
    }

    fn get(&mut self) -> &T {
        if self.value.is_none() {
            let init = self.init.take().unwrap();
            self.value = Some(init());
        }
        self.value.as_ref().unwrap()
    }
}

// Builder pattern with closure configuration
struct ServerBuilder {
    port: u16,
    handlers: Vec<Box<dyn Fn(&str) -> String>>,
}

impl ServerBuilder {
    fn new() -> Self {
        ServerBuilder {
            port: 8080,
            handlers: vec![],
        }
    }

    fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }

    fn handler<F>(mut self, f: F) -> Self
    where
        F: Fn(&str) -> String + 'static,
    {
        self.handlers.push(Box::new(f));
        self
    }
}

// Callback patterns
type Callback<T> = Box<dyn Fn(T) + Send + Sync>;

struct EventEmitter<T> {
    listeners: Vec<Callback<T>>,
}

impl<T: Clone> EventEmitter<T> {
    fn new() -> Self {
        EventEmitter { listeners: vec![] }
    }

    fn on<F>(&mut self, callback: F)
    where
        F: Fn(T) + Send + Sync + 'static,
    {
        self.listeners.push(Box::new(callback));
    }

    fn emit(&self, value: T) {
        for listener in &self.listeners {
            listener(value.clone());
        }
    }
}

fn advanced_patterns_demo() {
    // Memoized fibonacci
    let mut fib_memo = Memoizer::new(|n| {
        if n <= 1 { n } else {
            // Note: This naive recursion doesn't use memoization internally
            // Real implementation would need interior mutability
            n  // Placeholder
        }
    });

    // Lazy config loading
    let mut config = Lazy::new(|| {
        println!("Loading config...");
        std::collections::HashMap::from([
            ("key1", "value1"),
            ("key2", "value2"),
        ])
    });

    // Config not loaded yet
    println!("Getting config...");
    let _cfg = config.get();  // Now loads
    let _cfg = config.get();  // Uses cached value
}

Closures in Common Patterns

// Option/Result combinators
fn option_combinators() {
    let value: Option<i32> = Some(5);

    let doubled = value.map(|x| x * 2);
    let filtered = value.filter(|x| *x > 3);
    let or_else = value.or_else(|| Some(0));
    let unwrap_or = value.unwrap_or_else(|| {
        println!("Computing default...");
        0
    });
}

// Iterator closures
fn iterator_closures() {
    let numbers = vec![1, 2, 3, 4, 5];

    // Each of these takes a closure
    let _doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
    let _evens: Vec<_> = numbers.iter().filter(|x| *x % 2 == 0).collect();
    let _sum: i32 = numbers.iter().fold(0, |acc, x| acc + x);
    numbers.iter().for_each(|x| println!("{}", x));
}

// Thread spawning
fn thread_closure() {
    use std::thread;

    let data = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        // Closure moves data into thread
        println!("Data in thread: {:?}", data);
    });

    handle.join().unwrap();
}

// Sorting with closures
fn sorting_closures() {
    let mut numbers = vec![3, 1, 4, 1, 5, 9];

    // Sort with custom comparator
    numbers.sort_by(|a, b| b.cmp(a));  // Descending

    // Sort by key
    let mut people = vec![("Alice", 30), ("Bob", 25), ("Charlie", 35)];
    people.sort_by_key(|(_, age)| *age);
}

Best Practices

// 1. Prefer closures for short, inline functions
let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();

// 2. Use move for thread safety and 'static lifetimes
let handle = std::thread::spawn(move || { /* ... */ });

// 3. Choose appropriate trait bounds
// FnOnce: Maximum flexibility, single call
// FnMut: Multiple calls with mutation
// Fn: Multiple calls, no mutation

// 4. Use impl Trait for simple return types
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

// 5. Box closures for heterogeneous collections or dynamic dispatch
let callbacks: Vec<Box<dyn Fn()>> = vec![
    Box::new(|| println!("First")),
    Box::new(|| println!("Second")),
];

// 6. Avoid capturing more than needed
let expensive_data = vec![1; 1000000];
let len = expensive_data.len();  // Copy the length
let closure = move || println!("Length: {}", len);  // Only captures len
// expensive_data still accessible

Summary

Rust's advanced features work together to provide a powerful, safe, and performant systems programming language:

  • Traits enable polymorphism, abstraction, and code reuse through shared behavior
  • Generics allow writing flexible, type-safe code with zero runtime overhead
  • Error Handling with Result and Option makes failures explicit and recoverable
  • Smart Pointers provide safe memory management beyond simple references
  • Concurrency primitives enable fearless parallel programming with compile-time safety
  • Macros offer powerful metaprogramming for code generation and DSLs
  • Unsafe Rust provides escape hatches for low-level operations when needed
  • Async/Await enables efficient asynchronous programming for I/O-bound workloads
  • Iterators provide lazy, composable, and zero-cost sequence processing
  • Closures capture environment and enable functional programming patterns

The ownership system, combined with these features and zero-cost abstractions, enables writing high-level code that compiles to efficient machine code while maintaining memory safety and preventing data races at compile time.