Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 1: Foundation Patterns

This section covers fundamental design patterns that demonstrate core Rust concepts. Each pattern introduces new Rust features progressively, building your understanding of how Rust's ownership system, type safety, and zero-cost abstractions enable safer and more efficient implementations of traditional design patterns. After reading Chapter 1 you can write basic idiomatic Rust.

Patterns Covered

NewType Pattern

Wrapping existing types for type safety and semantic clarity

Core Rust concepts introduced:

  • Tuple Structs and the NewType Pattern
  • Ownership and Borrowing
  • String Types (String vs &str)
  • Derive Macros (Debug, Clone, Copy, etc.)
  • Result Type and Error Handling
  • Trait Implementation
  • Associated Functions vs Methods
  • Pattern Matching and Option Type
  • Visibility and Encapsulation
  • Type Safety Without Runtime Cost

Builder Pattern

Constructing complex objects step by step with fluent interfaces

New Rust concepts introduced:

  • Move Semantics in Method Chaining
  • Trait Bounds and Generic Programming (impl Into<String>)
  • PhantomData and Type-State Pattern
  • Type-Level Programming
  • Compile-Time Guarantees vs Runtime Checks
  • Use-After-Move Prevention
  • Zero-Cost State Machines

Iterator Pattern

Accessing elements sequentially with functional programming features

New Rust concepts introduced:

  • Lifetimes and Lifetime Parameters
  • Mutable References and Borrowing Rules
  • Closure Syntax and Capture
  • Iterator Combinators and Lazy Evaluation
  • Type Inference and the Turbofish (::<Type>)
  • Slice Types and Fat Pointers
  • Consuming vs Borrowing Iteration
  • Associated Types in Traits
  • Infinite Iterators and State

Result/Error Handling Pattern

Managing errors explicitly through the type system

New Rust concepts introduced:

  • Enums with Data and Pattern Matching
  • The ? Operator and Error Propagation
  • From Trait and Automatic Error Conversion
  • Result Combinators and Functional Error Handling
  • Error Display and Debug Traits
  • Unit Structs for Namespacing
  • Advanced Error Handling Patterns
  • Testing Error Conditions
  • Errors as Values Philosophy

RAII/Drop Pattern

Automatic resource management through scope-based cleanup

New Rust concepts introduced:

  • The Drop Trait and Automatic Cleanup
  • Scope-Based Resource Management (RAII)
  • Consuming Methods and Resource Transfer
  • Advanced Closure Patterns (FnOnce, FnMut, Fn)
  • Pattern Matching with Option::take()
  • Disarming Pattern
  • Unit Types and Zero-Sized Types
  • Generic Type Parameters with Trait Bounds
  • Resource Management Patterns
  • Testing Automatic Behavior

Learning Approach

Each pattern chapter follows this structure:

  1. Core Concept - What the pattern does and why it's useful
  2. Java Implementation - Traditional object-oriented approach
  3. Rust Implementation - How Rust improves upon the pattern
  4. Key Differences - Comparison highlighting Rust's advantages
  5. Core Rust Concepts Introduced - New language features demonstrated

By the end of these foundation patterns, you'll have a solid understanding of Rust's core concepts and be ready to tackle more advanced design patterns and architectural techniques.

NewType Pattern

The NewType pattern isn't one of the classic Gang of Four design patterns, but it's a fundamental Rust idiom that provides type safety and semantic clarity. It's particularly powerful in Rust due to the language's zero-cost abstractions and strong type system.

What is the NewType Pattern?

The NewType pattern involves wrapping an existing type in a new struct to create a distinct type with the same underlying representation but different semantics. This provides:

  • Type Safety: Prevents mixing up semantically different values of the same underlying type
  • API Control: You can implement only the traits and methods you want to expose
  • Zero Runtime Cost: The wrapper is eliminated at compile time
  • Clear Intent: Makes code more self-documenting

Comparison with Java

In Java, you might use wrapper classes, but they have runtime overhead:

// Java - has runtime overhead
public class UserId {
    private final int value;

    public UserId(int value) {
        this.value = value;
    }

    public int getValue() { return value; }
}

In Rust, the NewType pattern has zero runtime cost due to the compiler's optimizations.

Rust Implementation

use std::fmt;

// NewType pattern - wrapping primitive types for type safety
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(u32);

#[derive(Debug, Clone, PartialEq)]
pub struct Email(String);

// Implementing methods for NewType
impl UserId {
    pub fn new(id: u32) -> Self {
        UserId(id)
    }

    pub fn as_u32(&self) -> u32 {
        self.0
    }
}


impl Email {
    pub fn new(email: String) -> Result<Self, &'static str> {
        if email.contains('@') {
            Ok(Email(email))
        } else {
            Err("Invalid email format")
        }
    }

    pub fn domain(&self) -> Option<&str> {
        self.0.split('@').nth(1)
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

// Custom Display implementation
impl fmt::Display for UserId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "User#{}", self.0)
    }
}


impl fmt::Display for Email {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

// Example usage demonstrating type safety
pub struct User {
    id: UserId,
    email: Email,
}


impl User {
    pub fn new(id: u32, email: String) -> Result<Self, &'static str> {
        Ok(User {
            id: UserId::new(id),
            email: Email::new(email)?,
        })
    }

    pub fn id(&self) -> UserId {
        self.id
    }

    pub fn email(&self) -> &Email {
        &self.email
    }
}



fn main() {
    // Create some NewType instances
    let user_id = UserId::new(123);
    let email = Email::new("user@example.com".to_string()).unwrap();

    println!("User ID: {}", user_id);
    println!("Email domain: {:?}", email.domain());

    // Create user
    let user = User::new(123, "alice@example.com".to_string()).unwrap();

    // Demonstrate that NewTypes prevent accidental mixing
    let another_user_id = UserId::new(789);

    println!("User IDs can be compared: {}", user_id == another_user_id);
    println!("User ID as u32: {}", user_id.as_u32());
}

More Complex NewType Example

A more sophisticated example showing validation and behavior:

use std::fmt;

// Example: A more complex NewType with validation and behavior
#[derive(Debug, Clone, PartialEq)]
pub struct Temperature(f64);

impl Temperature {
    pub fn celsius(temp: f64) -> Self {
        Temperature(temp)
    }

    pub fn fahrenheit(temp: f64) -> Self {
        Temperature((temp - 32.0) * 5.0 / 9.0)
    }

    pub fn to_celsius(&self) -> f64 {
        self.0
    }

    pub fn to_fahrenheit(&self) -> f64 {
        self.0 * 9.0 / 5.0 + 32.0
    }

    pub fn to_kelvin(&self) -> f64 {
        self.0 + 273.15
    }
}

impl fmt::Display for Temperature {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:.1}°C", self.0)
    }
}

fn main() {
    // Temperature example
    let temp_c = Temperature::celsius(25.0);
    let temp_f = Temperature::fahrenheit(77.0);

    println!("Temperature in Celsius: {}", temp_c);
    println!("Temperature in Fahrenheit: {:.1}°F", temp_c.to_fahrenheit());
    println!("Temperature in Kelvin: {:.1}K", temp_c.to_kelvin());
}

Key Benefits Demonstrated

  1. Type Safety: UserId types can't be accidentally swapped with other wrapped types, even though they both wrap u32

  2. Zero Cost: The wrapper types are compiled away - no runtime overhead

  3. Controlled API: You decide which operations are available (notice we don't derive Add for IDs)

  4. Validation: The Email type enforces basic validation at construction time

  5. Semantic Clarity: Temperature::celsius(25.0) is much clearer than just 25.0

Advanced NewType Patterns

You can also implement traits selectively:

#![allow(unused)]
fn main() {
// Only implement specific traits you want
impl std::ops::Add for Temperature {
    type Output = Temperature;

    fn add(self, other: Temperature) -> Temperature {
        Temperature(self.0 + other.0)
    }
}
}

Relation to Gang of Four

While not a GoF pattern, NewType often works with other patterns:

  • Facade: NewTypes can simplify complex APIs
  • Adapter: Wrap foreign types to match your interface
  • Decorator: Add behavior to existing types without inheritance

The NewType pattern is particularly powerful in Rust because it leverages the type system for safety without runtime cost, something that's harder to achieve in classical OOP languages like Java.


Core Rust concepts introduced

1. Tuple Structs and the NewType Pattern

#![allow(unused)]
fn main() {
pub struct UserId(u32);
}
  • Tuple structs: Single-field structs with unnamed fields
  • NewType pattern: Wrapping existing types for type safety
  • Field access: Use .0 to access the wrapped value
  • Zero-cost abstraction: Wrapper is eliminated at compile time

2. Ownership and Borrowing

#![allow(unused)]
fn main() {
pub fn as_str(&self) -> &str {
    &self.0  // Borrowing the inner string data
}
}
  • Ownership: Every value has exactly one owner
  • Borrowing: Using & to create references without taking ownership
  • &self: Immutable borrow of the struct instance
  • &str: String slice (borrowed view of string data)
  • Memory safety: Prevents use-after-free and data races at compile time

3. String Types

#![allow(unused)]
fn main() {
pub struct Email(String);     // Owned string
pub fn as_str(&self) -> &str  // String slice (borrowed view)
}
  • String: Owned, heap-allocated, growable string
  • &str: Immutable string slice (view into string data)
  • Automatic conversion: String can be borrowed as &str
  • Memory efficiency: Pass &str instead of String when you don't need ownership

4. Derive Macros

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UserId(u32);
}
  • Automatic trait implementations: Compiler generates code for common traits
  • Debug: Enables {:?} and {:#?} formatting for debugging
  • Clone: Explicit copying with .clone() method
  • Copy: Implicit copying for simple stack-allocated types
  • PartialEq/Eq: Equality comparisons with == and !=
  • Hash: Enables use in HashMap, HashSet, etc.

5. Result Type and Error Handling

#![allow(unused)]
fn main() {
pub fn new(email: String) -> Result<Self, &'static str> {
    if email.contains('@') {
        Ok(Email(email))
    } else {
        Err("Invalid email format")
    }
}
}
  • Result<T, E>: Rust's approach to fallible operations
  • No exceptions: Errors are values, not thrown exceptions
  • Explicit handling: Must handle both Ok(value) and Err(error) cases
  • Type safety: Prevents forgotten error handling at compile time

6. Trait Implementation

#![allow(unused)]
fn main() {
impl fmt::Display for UserId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "User#{}", self.0)
    }
}
}
  • Traits: Similar to interfaces, define shared behavior
  • impl Trait for Type: Syntax for implementing traits on types
  • Method signature: &self for methods that borrow the instance
  • Return types: Traits can specify return types like fmt::Result

7. Associated Functions vs Methods

#![allow(unused)]
fn main() {
impl UserId {
    pub fn new(id: u32) -> Self {        // Associated function
        UserId(id)
    }

    pub fn as_u32(&self) -> u32 {        // Method
        self.0
    }
}
}
  • Associated functions: Don't take self, called with Type::function()
  • Methods: Take some form of self, called with instance.method()
  • Self type: Refers to the implementing type (here, UserId)
  • Constructor pattern: new() is conventionally an associated function

8. Pattern Matching and Option Type

#![allow(unused)]
fn main() {
pub fn domain(&self) -> Option<&str> {
    self.0.split('@').nth(1)  // Returns Option<&str>
}
}
  • Option<T>: Represents values that might not exist
  • Some(value): Contains a value
  • None: Represents absence of value
  • No null pointers: Rust eliminates null pointer exceptions
  • Explicit handling: Must handle both Some and None cases

9. Visibility and Encapsulation

#![allow(unused)]
fn main() {
pub struct UserId(u32);  // Public struct, but field is private by default
}
  • pub: Makes items publicly visible
  • Default privacy: Struct fields are private unless marked pub
  • Controlled access: Use methods to provide controlled access to data
  • Encapsulation: Hide implementation details while exposing safe interfaces

10. Type Safety Without Runtime Cost

#![allow(unused)]
fn main() {
// This won't compile - different types despite same underlying data:
// assert_eq!(user_id, another_different_type_id);  // Compile error!
}
  • Strong typing: Types that wrap the same data are still distinct
  • Compile-time guarantees: Type errors caught before runtime
  • Zero-cost abstractions: Safety checks happen at compile time
  • Performance: No runtime type checking needed

These fundamental concepts form the foundation for all Rust programming and will appear throughout the remaining pattern chapters. Understanding them here will make the subsequent patterns much clearer.

Builder Pattern

The Builder pattern is a creational design pattern that provides a way to construct complex objects step by step. It's particularly useful when you need to create objects with many optional parameters or when the construction process itself is complex and needs to be separated from the object's representation.

Core Concept

The Builder pattern solves the "telescoping constructor" problem where you'd otherwise need multiple constructors with different parameter combinations. Instead, it provides a fluent interface for building objects incrementally.

Java Implementation (Classical OOP)

Let's start with a traditional Java example:

// Product class
class Computer {
    private final String cpu;
    private final String ram;
    private final String storage;
    private final boolean hasGraphicsCard;
    private final boolean hasWifi;
    private final String operatingSystem;

    // Private constructor - can only be called by Builder
    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.storage = builder.storage;
        this.hasGraphicsCard = builder.hasGraphicsCard;
        this.hasWifi = builder.hasWifi;
        this.operatingSystem = builder.operatingSystem;
    }

    // Getters
    public String getCpu() { return cpu; }
    public String getRam() { return ram; }
    public String getStorage() { return storage; }
    public boolean hasGraphicsCard() { return hasGraphicsCard; }
    public boolean hasWifi() { return hasWifi; }
    public String getOperatingSystem() { return operatingSystem; }

    @Override
    public String toString() {
        return String.format(
            "Computer{cpu='%s', ram='%s', storage='%s', graphics=%b, wifi=%b, os='%s'}",
            cpu, ram, storage, hasGraphicsCard, hasWifi, operatingSystem
        );
    }

    // Static nested Builder class
    public static class Builder {
        // Required parameters
        private final String cpu;
        private final String ram;

        // Optional parameters with defaults
        private String storage = "256GB SSD";
        private boolean hasGraphicsCard = false;
        private boolean hasWifi = true;
        private String operatingSystem = "Windows 11";

        // Builder constructor with required parameters
        public Builder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        // Fluent interface methods
        public Builder storage(String storage) {
            this.storage = storage;
            return this;
        }

        public Builder withGraphicsCard() {
            this.hasGraphicsCard = true;
            return this;
        }

        public Builder withoutWifi() {
            this.hasWifi = false;
            return this;
        }

        public Builder operatingSystem(String os) {
            this.operatingSystem = os;
            return this;
        }

        // Build method creates the final object
        public Computer build() {
            return new Computer(this);
        }
    }
}

// Usage example
public class BuilderPatternDemo {
    public static void main(String[] args) {
        // Basic computer
        Computer basicComputer = new Computer.Builder("Intel i5", "8GB")
            .build();

        // Gaming computer
        Computer gamingComputer = new Computer.Builder("Intel i9", "32GB")
            .storage("1TB NVMe SSD")
            .withGraphicsCard()
            .operatingSystem("Windows 11 Pro")
            .build();

        // Server computer
        Computer serverComputer = new Computer.Builder("AMD Threadripper", "128GB")
            .storage("2TB SSD RAID")
            .withoutWifi()
            .operatingSystem("Ubuntu Server")
            .build();

        System.out.println(basicComputer);
        System.out.println(gamingComputer);
        System.out.println(serverComputer);
    }
}

Rust Implementation

Now let's see how we implement the Builder pattern in Rust. Rust's approach leverages its ownership system and type safety features:

// Product struct
#[derive(Debug, Clone)]
pub struct Computer {
    cpu: String,
    ram: String,
    storage: String,
    has_graphics_card: bool,
    has_wifi: bool,
    operating_system: String,
}

impl Computer {
    // Associated function to create a new builder
    pub fn builder(cpu: impl Into<String>, ram: impl Into<String>) -> ComputerBuilder {
        ComputerBuilder::new(cpu, ram)
    }

    // Getters
    pub fn cpu(&self) -> &str { &self.cpu }
    pub fn ram(&self) -> &str { &self.ram }
    pub fn storage(&self) -> &str { &self.storage }
    pub fn has_graphics_card(&self) -> bool { self.has_graphics_card }
    pub fn has_wifi(&self) -> bool { self.has_wifi }
    pub fn operating_system(&self) -> &str { &self.operating_system }
}

// Builder struct
pub struct ComputerBuilder {
    cpu: String,
    ram: String,
    storage: String,
    has_graphics_card: bool,
    has_wifi: bool,
    operating_system: String,
}

impl ComputerBuilder {
    // Constructor with required parameters
    pub fn new(cpu: impl Into<String>, ram: impl Into<String>) -> Self {
        Self {
            cpu: cpu.into(),
            ram: ram.into(),
            storage: "256GB SSD".to_string(),
            has_graphics_card: false,
            has_wifi: true,
            operating_system: "Windows 11".to_string(),
        }
    }

    // Fluent interface methods - each takes ownership and returns Self
    pub fn storage(mut self, storage: impl Into<String>) -> Self {
        self.storage = storage.into();
        self
    }

    pub fn with_graphics_card(mut self) -> Self {
        self.has_graphics_card = true;
        self
    }

    pub fn without_wifi(mut self) -> Self {
        self.has_wifi = false;
        self
    }

    pub fn operating_system(mut self, os: impl Into<String>) -> Self {
        self.operating_system = os.into();
        self
    }

    // Build method consumes the builder and returns the final product
    pub fn build(self) -> Computer {
        Computer {
            cpu: self.cpu,
            ram: self.ram,
            storage: self.storage,
            has_graphics_card: self.has_graphics_card,
            has_wifi: self.has_wifi,
            operating_system: self.operating_system,
        }
    }
}

// Advanced: Type-state pattern for compile-time validation
// This ensures required fields are set at compile time

pub struct CpuSet;
pub struct RamSet;
pub struct NotSet;

pub struct TypedComputerBuilder<CpuState = NotSet, RamState = NotSet> {
    cpu: Option<String>,
    ram: Option<String>,
    storage: String,
    has_graphics_card: bool,
    has_wifi: bool,
    operating_system: String,
    _cpu_state: std::marker::PhantomData<CpuState>,
    _ram_state: std::marker::PhantomData<RamState>,
}

impl TypedComputerBuilder<NotSet, NotSet> {
    pub fn new() -> Self {
        Self {
            cpu: None,
            ram: None,
            storage: "256GB SSD".to_string(),
            has_graphics_card: false,
            has_wifi: true,
            operating_system: "Windows 11".to_string(),
            _cpu_state: std::marker::PhantomData,
            _ram_state: std::marker::PhantomData,
        }
    }

    pub fn cpu(self, cpu: impl Into<String>) -> TypedComputerBuilder<CpuSet, NotSet> {
        TypedComputerBuilder {
            cpu: Some(cpu.into()),
            ram: self.ram,
            storage: self.storage,
            has_graphics_card: self.has_graphics_card,
            has_wifi: self.has_wifi,
            operating_system: self.operating_system,
            _cpu_state: std::marker::PhantomData,
            _ram_state: std::marker::PhantomData,
        }
    }
}

impl TypedComputerBuilder<CpuSet, NotSet> {
    pub fn ram(self, ram: impl Into<String>) -> TypedComputerBuilder<CpuSet, RamSet> {
        TypedComputerBuilder {
            cpu: self.cpu,
            ram: Some(ram.into()),
            storage: self.storage,
            has_graphics_card: self.has_graphics_card,
            has_wifi: self.has_wifi,
            operating_system: self.operating_system,
            _cpu_state: std::marker::PhantomData,
            _ram_state: std::marker::PhantomData,
        }
    }
}

impl<CpuState, RamState> TypedComputerBuilder<CpuState, RamState> {
    pub fn storage(mut self, storage: impl Into<String>) -> Self {
        self.storage = storage.into();
        self
    }

    pub fn with_graphics_card(mut self) -> Self {
        self.has_graphics_card = true;
        self
    }

    pub fn without_wifi(mut self) -> Self {
        self.has_wifi = false;
        self
    }

    pub fn operating_system(mut self, os: impl Into<String>) -> Self {
        self.operating_system = os.into();
        self
    }
}

// Only allow building when all required fields are set
impl TypedComputerBuilder<CpuSet, RamSet> {
    pub fn build(self) -> Computer {
        Computer {
            cpu: self.cpu.unwrap(),
            ram: self.ram.unwrap(),
            storage: self.storage,
            has_graphics_card: self.has_graphics_card,
            has_wifi: self.has_wifi,
            operating_system: self.operating_system,
        }
    }
}

fn main() {
    // Simple builder usage
    let basic_computer = Computer::builder("Intel i5", "8GB")
        .build();

    let gaming_computer = Computer::builder("Intel i9", "32GB")
        .storage("1TB NVMe SSD")
        .with_graphics_card()
        .operating_system("Windows 11 Pro")
        .build();

    let server_computer = Computer::builder("AMD Threadripper", "128GB")
        .storage("2TB SSD RAID")
        .without_wifi()
        .operating_system("Ubuntu Server")
        .build();

    println!("{:?}", basic_computer);
    println!("{:?}", gaming_computer);
    println!("{:?}", server_computer);

    // Type-state builder usage (compile-time safety)
    let typed_computer = TypedComputerBuilder::new()
        .cpu("Intel i7")
        .ram("16GB")
        .storage("512GB SSD")
        .with_graphics_card()
        .build();

    println!("{:?}", typed_computer);

    // This would cause a compile error:
    // let incomplete = TypedComputerBuilder::new()
    //     .cpu("Intel i7")
    //     .build(); // Error: ram not set!
}

Key Differences Between Java and Rust Implementations

1. Ownership and Move Semantics

  • Java: Uses return this to enable method chaining, keeping the same object reference
  • Rust: Methods take self by value and return Self, using move semantics for the fluent interface

2. Memory Management

  • Java: Relies on garbage collection; the builder object persists until GC
  • Rust: The builder is consumed when build() is called, preventing use-after-build errors at compile time

3. Type Safety

  • Java: Runtime validation if required fields aren't set
  • Rust: Can use the type-state pattern to enforce required fields at compile time

4. String Handling

  • Java: Uses String directly with implicit conversions
  • Rust: Uses impl Into<String> for flexible string input (&str, String, etc.)

5. Null Safety

  • Java: Potential for null pointer exceptions
  • Rust: No null values; uses Option<T> when needed

When to Use the Builder Pattern

The Builder pattern is ideal when:

  • Objects have many optional parameters
  • Object construction is complex or requires validation
  • You want to enforce immutability in the final object
  • The construction process should be independent of the object's representation

The Rust implementation particularly shines with its compile-time safety guarantees and zero-cost abstractions, making it both safer and potentially more performant than traditional OOP implementations.


Core Rust concepts introduced

Building on the concepts from the NewType pattern, the Builder pattern introduces several new Rust concepts:

1. Move Semantics in Method Chaining

#![allow(unused)]
fn main() {
pub fn storage(mut self, storage: impl Into<String>) -> Self {
    self.storage = storage.into();
    self
}
}
  • Taking self by value: Each builder method consumes the builder and returns a new one
  • Move semantics: The builder is moved through the chain, preventing accidental reuse
  • Different from references: Unlike Java's return this, Rust actually moves the data
  • mut self: Explicitly declares that the method can modify the moved value

2. Trait Bounds and Generic Programming

#![allow(unused)]
fn main() {
pub fn new(cpu: impl Into<String>, ram: impl Into<String>) -> Self
}
  • impl Into<String>: A trait bound allowing any type convertible to String
  • Flexible input: Accepts &str, String, Cow<str>, etc. without explicit conversion
  • Compile-time resolution: More efficient than Java's method overloading
  • Generic constraints: Specify what capabilities a type parameter must have

3. Advanced: PhantomData and Type-State Pattern

#![allow(unused)]
fn main() {
pub struct TypedComputerBuilder<CpuState = NotSet, RamState = NotSet> {
    _cpu_state: std::marker::PhantomData<CpuState>,
    _ram_state: std::marker::PhantomData<RamState>,
}
}
  • Generic type parameters: Track builder state at compile time
  • Default type parameters: = NotSet provides defaults for generics
  • PhantomData: Zero-sized type that carries type information without runtime cost
  • Compile-time state tracking: Prevents calling build() before required fields are set

4. Type-Level Programming

#![allow(unused)]
fn main() {
impl TypedComputerBuilder<NotSet, NotSet> {
    pub fn cpu(self) -> TypedComputerBuilder<CpuSet, NotSet> { ... }
}

impl TypedComputerBuilder<CpuSet, NotSet> {
    pub fn ram(self) -> TypedComputerBuilder<CpuSet, RamSet> { ... }
}

// Only this implementation has build()
impl TypedComputerBuilder<CpuSet, RamSet> {
    pub fn build(self) -> Computer { ... }
}
}
  • Different implementations for different type states: Compiler selects the right impl block
  • Encoding logic in types: Use the type system to enforce correct usage patterns
  • Compile-time method availability: Some methods only exist for certain type states
  • State transitions: Methods transform types from one state to another

5. Compile-Time Guarantees vs Runtime Checks

#![allow(unused)]
fn main() {
pub fn build(self) -> Computer {  // Returns Computer, not Option<Computer>
    Computer {
        cpu: self.cpu.unwrap(),  // Safe because type system guarantees this exists
        // ...
    }
}
}
  • Type-safe unwrapping: unwrap() is safe when types guarantee the value exists
  • Eliminating runtime errors: Move error checking from runtime to compile time
  • Performance benefit: No need for runtime validation when types encode correctness

6. Use-After-Move Prevention

#![allow(unused)]
fn main() {
let builder = ComputerBuilder::new("Intel i7", "16GB");
let computer = builder.build();  // builder is consumed here
// builder.storage("1TB SSD");  // ❌ Compile error: builder was moved
}
  • Consuming methods: Methods that take self by value consume the object
  • Preventing reuse bugs: Can't accidentally use a builder after calling build()
  • Memory safety: Eliminates entire classes of bugs at compile time

7. Zero-Cost State Machines

The type-state pattern demonstrates:

  • Compile-time state machines: State transitions verified at compile time
  • Zero runtime overhead: All type-state information is erased during compilation
  • API safety: Impossible to call methods in wrong order or wrong state
  • Documentation through types: The type system serves as living documentation

These advanced concepts showcase Rust's unique ability to encode complex invariants in the type system, providing both safety and performance benefits that are difficult to achieve in traditional object-oriented languages.

Iterator Pattern

The Iterator pattern provides a way to access elements of a collection sequentially without exposing the underlying representation of the collection. It's one of the most fundamental behavioral patterns and is deeply integrated into many modern programming languages.

Core Concept

The Iterator pattern defines a standard interface for traversing collections, allowing you to:

  • Access elements one by one without knowing the internal structure
  • Support multiple simultaneous traversals of the same collection
  • Provide a uniform interface for different collection types

Java Implementation (Classical OOP)

In Java, the Iterator pattern is typically implemented using the Iterator interface:

import java.util.*;

// Custom collection implementing Iterable
class BookCollection implements Iterable<String> {
    private List<String> books = new ArrayList<>();

    public void addBook(String book) {
        books.add(book);
    }

    @Override
    public Iterator<String> iterator() {
        return new BookIterator();
    }

    // Inner class implementing Iterator
    private class BookIterator implements Iterator<String> {
        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < books.size();
        }

        @Override
        public String next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return books.get(currentIndex++);
        }
    }
}

// Usage example
public class IteratorExample {
    public static void main(String[] args) {
        BookCollection library = new BookCollection();
        library.addBook("Design Patterns");
        library.addBook("Clean Code");
        library.addBook("Refactoring");

        // Using iterator explicitly
        Iterator<String> iter = library.iterator();
        while (iter.hasNext()) {
            System.out.println(iter.next());
        }

        // Using enhanced for-loop (syntactic sugar over iterator)
        for (String book : library) {
            System.out.println(book);
        }
    }
}

Rust Implementation

Rust takes a different approach with its iterator system, making it more functional and zero-cost through compile-time optimizations. Rust has two main iterator traits: Iterator and IntoIterator.

use std::vec::IntoIter;

// Custom collection
#[derive(Debug)]
struct BookCollection {
    books: Vec<String>,
}

impl BookCollection {
    fn new() -> Self {
        Self { books: Vec::new() }
    }

    fn add_book(&mut self, book: String) {
        self.books.push(book);
    }
}

// Implementing IntoIterator for owned values
impl IntoIterator for BookCollection {
    type Item = String;
    type IntoIter = IntoIter<String>;

    fn into_iter(self) -> Self::IntoIter {
        self.books.into_iter()
    }
}

// Implementing IntoIterator for references (borrowing)
impl<'a> IntoIterator for &'a BookCollection {
    type Item = &'a String;
    type IntoIter = std::slice::Iter<'a, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.books.iter()
    }
}

// Custom iterator implementation
struct BookIterator<'a> {
    books: &'a [String],
    index: usize,
}

impl<'a> BookIterator<'a> {
    fn new(books: &'a [String]) -> Self {
        Self { books, index: 0 }
    }
}

impl<'a> Iterator for BookIterator<'a> {
    type Item = &'a String;

    fn next(&mut self) -> Option<Self::Item> {
        if self.index < self.books.len() {
            let book = &self.books[self.index];
            self.index += 1;
            Some(book)
        } else {
            None
        }
    }
}

impl BookCollection {
    // Method returning custom iterator
    fn iter(&self) -> BookIterator<'_> {
        BookIterator::new(&self.books)
    }
}

fn main() {
    let mut library = BookCollection::new();
    library.add_book("Design Patterns".to_string());
    library.add_book("Clean Code".to_string());
    library.add_book("Refactoring".to_string());

    // Using custom iterator
    println!("Using custom iterator:");
    for book in library.iter() {
        println!("{}", book);
    }

    // Using IntoIterator for references
    println!("\nUsing IntoIterator for references:");
    for book in &library {
        println!("{}", book);
    }

    // Using iterator combinators (functional style)
    println!("\nUsing iterator combinators:");
    let uppercase_books: Vec<String> = library.iter()
        .map(|book| book.to_uppercase())
        .collect();

    for book in &uppercase_books {
        println!("{}", book);
    }

    // Consuming the collection with IntoIterator
    println!("\nConsuming iteration:");
    for book in library { // This moves library
        println!("Owned: {}", book);
    }
    // library is no longer accessible here due to move
}

// Advanced example: Lazy iterator with state
struct FibonacciIterator {
    current: u64,
    next: u64,
}

impl FibonacciIterator {
    fn new() -> Self {
        Self { current: 0, next: 1 }
    }
}

impl Iterator for FibonacciIterator {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let current = self.current;
        self.current = self.next;
        self.next = current + self.next;

        // Prevent overflow by stopping at large numbers
        if current > 1_000_000 {
            None
        } else {
            Some(current)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fibonacci_iterator() {
        let fib: Vec<u64> = FibonacciIterator::new()
            .take(10)
            .collect();

        assert_eq!(fib, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
    }

    #[test]
    fn test_book_collection_iterator() {
        let mut library = BookCollection::new();
        library.add_book("Book 1".to_string());
        library.add_book("Book 2".to_string());

        let books: Vec<&String> = library.iter().collect();
        assert_eq!(books.len(), 2);
    }
}

Key Differences Between Java and Rust

Java Approach:

  • Object-oriented with explicit iterator objects
  • Manual memory management through garbage collection
  • Exception-based error handling (NoSuchElementException)
  • Mutable iterator state through instance variables
  • Enhanced for-loop as syntactic sugar

Rust Approach:

  • Functional programming style with zero-cost abstractions
  • Ownership system eliminates need for garbage collection
  • Option-based error handling (None instead of exceptions)
  • Immutable by default with explicit mutability
  • Rich set of iterator combinators (map, filter, fold, etc.)
  • Lazy evaluation - iterators do nothing until consumed

Benefits of Rust's Iterator Design

  1. Zero-cost abstractions: Rust's iterators compile down to the same code as hand-written loops
  2. Memory safety: The ownership system prevents iterator invalidation bugs
  3. Functional composition: Chain operations together naturally
  4. Lazy evaluation: Only compute values when needed
  5. Explicit ownership: Clear distinction between borrowing and consuming iteration

The Iterator pattern in Rust demonstrates how the language takes classical design patterns and reimagines them through the lens of ownership, safety, and zero-cost abstractions, often resulting in more expressive and efficient code than traditional OOP implementations.


Core Rust concepts introduced

Building on the NewType and Builder patterns, the Iterator pattern introduces several new concepts:

1. Lifetimes and Lifetime Parameters

#![allow(unused)]
fn main() {
impl<'a> IntoIterator for &'a BookCollection {
    type Item = &'a String;
    type IntoIter = std::slice::Iter<'a, String>;
}

struct BookIterator<'a> {
    books: &'a [String],
    index: usize,
}
}
  • Lifetime parameters ('a): Tell the compiler how long references are valid
  • Lifetime annotations: Connect the lifetime of input and output references
  • Borrow checker validation: Ensures references don't outlive the data they point to
  • Generic over lifetimes: Structs and functions can be parameterized by lifetimes

2. Mutable References and Borrowing Rules

#![allow(unused)]
fn main() {
fn next(&mut self) -> Option<Self::Item> {
    self.index += 1;  // Can modify because of &mut self
    // ...
}

fn iter(&self) -> BookIterator {
    // Cannot modify self, only read
    BookIterator::new(&self.books)
}
}
  • &mut self: Exclusive mutable access to modify the iterator state
  • Borrowing rules: Only one mutable reference OR multiple immutable references
  • Interior mutability: Some types allow mutation through shared references (not shown here)

3. Closure Syntax and Capture

#![allow(unused)]
fn main() {
.map(|book| book.to_uppercase())
.filter(|&book| book.len() > 5)
}
  • Closure syntax: |param| expression for anonymous functions
  • Capture by reference: Closures can borrow values from their environment
  • Capture by value: Use move keyword to take ownership of captured variables
  • Closure traits: Fn, FnMut, and FnOnce define how closures interact with captured data

4. Iterator Combinators and Lazy Evaluation

#![allow(unused)]
fn main() {
let uppercase_books: Vec<String> = library.iter()
    .map(|book| book.to_uppercase())    // Transform each element
    .filter(|book| book.contains("RUST"))  // Keep only matching elements
    .collect();                         // Consume iterator into collection
}
  • Lazy evaluation: Iterators do nothing until consumed by methods like collect()
  • Method chaining: Combine multiple operations in a fluent interface
  • Adapters vs consumers: map/filter are adapters, collect/fold are consumers
  • Zero-cost: The entire chain compiles to an efficient loop

5. Type Inference and the Turbofish

#![allow(unused)]
fn main() {
let books: Vec<&String> = library.iter().collect();
// Or with explicit type specification:
let books = library.iter().collect::<Vec<&String>>();
}
  • Type inference: Rust deduces types from context when possible
  • Turbofish syntax (::<Type>): Explicitly specify generic type parameters
  • When inference fails: Need explicit types when compiler can't determine the target type

6. Slice Types and Fat Pointers

#![allow(unused)]
fn main() {
struct BookIterator<'a> {
    books: &'a [String],  // Slice reference
    index: usize,
}
}
  • Slice types (&[T]): References to a contiguous sequence of elements
  • Fat pointers: Slices contain both a pointer and a length
  • No bounds checking overhead: Slice length is known at runtime
  • Memory efficient: Just a reference to existing data, no allocation

7. Consuming vs Borrowing Iteration

#![allow(unused)]
fn main() {
// Borrowing iteration - collection remains usable
for book in &library {
    println!("{}", book);
}

// Consuming iteration - collection is moved
for book in library {  // library is no longer accessible after this
    println!("Owned: {}", book);
}
}
  • Different iteration modes: &collection vs collection vs &mut collection
  • Move semantics in loops: for book in library moves the entire collection
  • Preventing use-after-move: Compiler error if you try to use library after consuming iteration

8. Associated Types in Traits

#![allow(unused)]
fn main() {
impl Iterator for BookIterator<'a> {
    type Item = &'a String;  // Associated type

    fn next(&mut self) -> Option<Self::Item> {
        // ...
    }
}
}
  • Associated types: Types that are "associated" with a trait implementation
  • Self::Item: Refers to the associated type within the implementation
  • Cleaner than generics: Often more readable than generic type parameters

9. Infinite Iterators and State

#![allow(unused)]
fn main() {
struct FibonacciIterator {
    current: u64,
    next: u64,
}

impl Iterator for FibonacciIterator {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let current = self.current;
        self.current = self.next;
        self.next = current + self.next;

        if current > 1_000_000 {
            None  // Stop at large numbers
        } else {
            Some(current)
        }
    }
}
}
  • Stateful iterators: Iterators can maintain internal state between calls
  • Infinite sequences: Iterators don't need to have a predetermined end
  • Conditional termination: Return None to signal the end of iteration

These concepts demonstrate Rust's approach to safe, efficient iteration that eliminates common bugs like iterator invalidation while providing zero-cost abstractions and powerful functional programming capabilities.

Result/Error Handling Pattern

The Result/Error Handling pattern in Rust is a fundamental approach to error management that makes error handling explicit and type-safe. While not strictly one of the Gang of Four design patterns, it's a crucial pattern in Rust that provides a robust alternative to exception-based error handling found in classical OOP languages like Java.

What is the Result Pattern?

The Result pattern uses Rust's Result<T, E> enum to represent either a successful value (Ok(T)) or an error (Err(E)). This forces developers to explicitly handle both success and failure cases, preventing runtime crashes from unhandled exceptions.

Comparison with Java

In Java, error handling typically uses exceptions:

// Java - Exception-based error handling
public class FileProcessor {
    public String readFile(String filename) throws IOException {
        // May throw IOException
        return Files.readString(Paths.get(filename));
    }

    public void processFile(String filename) {
        try {
            String content = readFile(filename);
            System.out.println("Content: " + content);
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }
}

Rust Implementation

use std::fs;
use std::io;
use std::fmt;

// Custom error type for our application
#[derive(Debug)]
enum FileProcessError {
    IoError(io::Error),
    InvalidContent(String),
    EmptyFile,
}

impl fmt::Display for FileProcessError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FileProcessError::IoError(err) => write!(f, "IO error: {}", err),
            FileProcessError::InvalidContent(msg) => write!(f, "Invalid content: {}", msg),
            FileProcessError::EmptyFile => write!(f, "File is empty"),
        }
    }
}

impl From<io::Error> for FileProcessError {
    fn from(error: io::Error) -> Self {
        FileProcessError::IoError(error)
    }
}

struct FileProcessor;

impl FileProcessor {
    // Returns Result<T, E> instead of throwing exceptions
    fn read_file(filename: &str) -> Result<String, FileProcessError> {
        let content = fs::read_to_string(filename)?; // ? operator for error propagation

        if content.is_empty() {
            return Err(FileProcessError::EmptyFile);
        }

        Ok(content)
    }

    // Validates file content
    fn validate_content(content: &str) -> Result<&str, FileProcessError> {
        if content.len() < 10 {
            return Err(FileProcessError::InvalidContent(
                "Content too short".to_string()
            ));
        }
        Ok(content)
    }

    // Chains multiple Result operations
    fn process_file(filename: &str) -> Result<String, FileProcessError> {
        let content = Self::read_file(filename)?;
        let validated_content = Self::validate_content(&content)?;

        // Process the content (just uppercase for this example)
        Ok(validated_content.to_uppercase())
    }
}

fn main() {
    let filenames = vec!["example.txt", "nonexistent.txt", "empty.txt"];

    for filename in filenames {
        // Pattern matching for error handling
        match FileProcessor::process_file(filename) {
            Ok(processed_content) => {
                println!("✅ Successfully processed {}: {}", filename,
                        &processed_content[..50.min(processed_content.len())]);
            }
            Err(error) => {
                println!("❌ Error processing {}: {}", filename, error);
            }
        }
    }

    // Alternative error handling approaches
    demonstrate_error_handling_patterns();
}

fn demonstrate_error_handling_patterns() {
    println!("\n--- Different Error Handling Patterns ---");

    // 1. Using unwrap_or for default values
    let result1 = FileProcessor::read_file("nonexistent.txt")
        .unwrap_or_else(|_| "Default content".to_string());
    println!("With default: {}", result1);

    // 2. Using map and map_err for transformations
    let result2 = FileProcessor::read_file("example.txt")
        .map(|content| content.len())
        .map_err(|err| format!("Transformed error: {}", err));

    match result2 {
        Ok(length) => println!("Content length: {}", length),
        Err(err) => println!("Error: {}", err),
    }

    // 3. Chaining operations with and_then
    let result3 = FileProcessor::read_file("example.txt")
        .and_then(|content| {
            FileProcessor::validate_content(&content)?;
            Ok(content)
        })
        .map(|content| format!("Processed: {}", content.to_uppercase()));

    match result3 {
        Ok(processed) => println!("{}", processed),
        Err(err) => println!("Chain error: {}", err),
    }
}

// Demonstrating the ? operator sugar
fn complex_operation(filename: &str) -> Result<usize, FileProcessError> {
    // Each ? will return early if there's an error
    let content = FileProcessor::read_file(filename)?;
    let validated = FileProcessor::validate_content(&content)?;
    let processed = validated.to_uppercase();

    Ok(processed.len())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_successful_processing() {
        // Create a temporary file for testing
        std::fs::write("test_file.txt", "This is a test file with enough content").unwrap();

        let result = FileProcessor::process_file("test_file.txt");
        assert!(result.is_ok());

        // Cleanup
        std::fs::remove_file("test_file.txt").ok();
    }

    #[test]
    fn test_empty_file_error() {
        // Create an empty file
        std::fs::write("empty_test.txt", "").unwrap();

        let result = FileProcessor::read_file("empty_test.txt");
        assert!(matches!(result, Err(FileProcessError::EmptyFile)));

        // Cleanup
        std::fs::remove_file("empty_test.txt").ok();
    }
}

Key Advantages of Rust's Result Pattern

**1. Explicit Error Handling: Unlike Java's checked exceptions that can be ignored or forgotten, Rust's Result type forces you to handle errors explicitly.

**2. No Hidden Control Flow: Unlike exceptions that can unwind the stack unpredictably, Results make error propagation explicit and traceable.

**3. Composability: Results can be chained using methods like map, and_then, unwrap_or, making error handling both functional and readable.

**4. Zero-Cost Abstractions: The ? operator provides clean syntax without runtime overhead.

**5. Type Safety: The compiler ensures you handle all possible error cases, preventing runtime crashes from unhandled exceptions.

Comparison Summary

AspectJava (Exceptions)Rust (Result)
Error VisibilityHidden until runtimeExplicit in type system
Handling RequirementOptional (can ignore)Mandatory
PerformanceStack unwinding overheadZero-cost
ComposabilityLimitedHighly composable
Control FlowNon-local (jumps)Local and explicit

The Result pattern exemplifies Rust's philosophy of "making errors impossible to ignore" while providing ergonomic tools for error handling that are both safer and more performant than traditional exception-based approaches.


Core Rust concepts introduced

Building on the previous patterns, the Error Handling pattern introduces several new concepts:

1. Enums with Data and Pattern Matching

#![allow(unused)]
fn main() {
#[derive(Debug)]
enum FileProcessError {
    IoError(io::Error),        // Variant holding another error type
    InvalidContent(String),     // Variant holding a String
    EmptyFile,                 // Unit variant (no data)
}

match FileProcessor::process_file(filename) {
    Ok(processed_content) => { /* handle success */ }
    Err(error) => { /* handle error */ }
}
}
  • Enum variants with data: Each variant can hold different types of data
  • Exhaustive pattern matching: match requires handling all possible cases
  • Destructuring in patterns: Extract data from enum variants
  • Sum types: Enums represent "one of" several possible values

2. The ? Operator and Error Propagation

#![allow(unused)]
fn main() {
let content = fs::read_to_string(filename)?;  // Early return on error
let validated = Self::validate_content(&content)?;
}
  • Early return shorthand: ? returns immediately if Result is Err
  • Automatic conversion: Uses From trait to convert between error types
  • Syntactic sugar: Eliminates verbose match statements for error propagation
  • Function signature requirement: Function must return Result to use ?

3. From Trait and Automatic Error Conversion

#![allow(unused)]
fn main() {
impl From<io::Error> for FileProcessError {
    fn from(error: io::Error) -> Self {
        FileProcessError::IoError(error)
    }
}
}
  • Automatic conversions: ? operator uses From implementations
  • Error type unification: Convert different error types to a common error type
  • Composable error handling: Build error hierarchies without manual conversion
  • Zero-cost conversion: From implementations are inlined

4. Result Combinators and Functional Error Handling

#![allow(unused)]
fn main() {
let result = FileProcessor::read_file("file.txt")
    .map(|content| content.len())                    // Transform success value
    .map_err(|err| format!("Error: {}", err))       // Transform error value
    .and_then(|len| if len > 0 { Ok(len) } else { Err("Empty".to_string()) });
}
  • Functional composition: Chain operations that might fail
  • map: Transform the success value, leave errors unchanged
  • map_err: Transform the error value, leave success unchanged
  • and_then: Chain operations that also return Result
  • Monadic interface: Results form a monad for error handling

5. Error Display and Debug Traits

#![allow(unused)]
fn main() {
impl fmt::Display for FileProcessError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FileProcessError::IoError(err) => write!(f, "IO error: {}", err),
            FileProcessError::InvalidContent(msg) => write!(f, "Invalid content: {}", msg),
            FileProcessError::EmptyFile => write!(f, "File is empty"),
        }
    }
}
}
  • Display trait: Defines user-facing error messages
  • Debug trait: Defines debugging representation (via #[derive(Debug)])
  • Error trait: Standard trait for error types (not shown but commonly used)
  • Hierarchical error information: Errors can wrap and display other errors

6. Unit Structs for Namespacing

#![allow(unused)]
fn main() {
struct FileProcessor;  // Unit struct with no fields

impl FileProcessor {
    fn read_file(filename: &str) -> Result<String, FileProcessError> { /* ... */ }
}
}
  • Unit structs: Types with no data, used for namespacing
  • Zero-size types: No runtime memory cost
  • Associated functions: Group related functions under a type
  • No inheritance: Use modules and types for organization instead

7. Advanced Error Handling Patterns

#![allow(unused)]
fn main() {
// Chaining with and_then for complex validation
let result = FileProcessor::read_file("file.txt")
    .and_then(|content| {
        FileProcessor::validate_content(&content)?;
        Ok(content)
    })
    .map(|content| content.to_uppercase());
}
  • Error short-circuiting: First error stops the entire chain
  • Nested error handling: Use ? inside closures passed to and_then
  • Transformation pipelines: Build complex processing pipelines
  • Fail-fast behavior: Errors propagate immediately without executing later steps

8. Testing Error Conditions

#![allow(unused)]
fn main() {
#[test]
fn test_empty_file_error() {
    let result = FileProcessor::read_file("empty_test.txt");
    assert!(matches!(result, Err(FileProcessError::EmptyFile)));
}
}
  • matches! macro: Pattern matching in assertions
  • Testing error paths: Verify that errors occur under expected conditions
  • Specific error checking: Test for exact error variants, not just failure

9. Errors as Values Philosophy

This pattern demonstrates Rust's fundamental approach to error handling:

  • No hidden control flow: Errors don't "jump" through the call stack
  • Explicit in types: Function signatures show whether they can fail
  • Composable: Errors can be transformed, chained, and combined
  • Performance: No stack unwinding or exception handling overhead

These concepts show how Rust's type system makes error handling both safer and more explicit than exception-based systems, while providing powerful tools for composing error-handling logic.

RAII/Dropdown Pattern

The RAII (Resource Acquisition Is Initialization) pattern, combined with Rust's Drop trait, is a fundamental resource management pattern that ensures resources are automatically cleaned up when they go out of scope. This isn't one of the original Gang of Four patterns, but it's so central to Rust's design philosophy that it's worth understanding in the context of design patterns.

What is RAII/Drop?

RAII is a programming idiom where resource acquisition (memory allocation, file handles, network connections, etc.) is tied to object initialization, and resource deallocation is tied to object destruction. In Rust, this is implemented through the Drop trait, which provides a drop method that's automatically called when a value goes out of scope.

Comparison with Java

In Java, you typically manage resources using try-with-resources or manual cleanup:

// Java - Manual resource management
public class FileManager {
    private FileInputStream file;

    public FileManager(String filename) throws IOException {
        this.file = new FileInputStream(filename);
    }

    public void close() throws IOException {
        if (file != null) {
            file.close();
        }
    }

    // Or using try-with-resources
    public static void processFile(String filename) {
        try (FileInputStream file = new FileInputStream(filename)) {
            // Use file
        } catch (IOException e) {
            // Handle error
        }
    }
}

Rust Implementation

use std::fs::File;
use std::io::{BufRead, BufReader, Read};

// Example 1: Custom resource with Drop implementation
struct DatabaseConnection {
    connection_id: u32,
    is_connected: bool,
}

impl DatabaseConnection {
    fn new(id: u32) -> Self {
        println!("Opening database connection {}", id);
        DatabaseConnection {
            connection_id: id,
            is_connected: true,
        }
    }

    fn execute_query(&self, query: &str) -> std::result::Result<Vec<String>, Box<dyn std::error::Error>> {
        if !self.is_connected {
            return Err("Connection is closed".into());
        }
        println!("Executing query on connection {}: {}", self.connection_id, query);
        Ok(vec!["result1".to_string(), "result2".to_string()])
    }
}

// Implement Drop trait for automatic cleanup
impl Drop for DatabaseConnection {
    fn drop(&mut self) {
        if self.is_connected {
            println!("Closing database connection {}", self.connection_id);
            self.is_connected = false;
        }
    }
}

// Example 2: File manager using RAII
struct FileManager {
    file: BufReader<File>,
    filename: String,
}

impl FileManager {
    fn new(filename: &str) -> std::io::Result<Self> {
        println!("Opening file: {}", filename);
        let file = File::open(filename)?;
        let reader = BufReader::new(file);

        Ok(FileManager {
            file: reader,
            filename: filename.to_string(),
        })
    }

    fn read_lines(&mut self) -> std::io::Result<Vec<String>> {
        let mut lines = Vec::new();
        for line in self.file.by_ref().lines() {
            lines.push(line?);
        }
        Ok(lines)
    }
}

impl Drop for FileManager {
    fn drop(&mut self) {
        println!("Closing file: {}", self.filename);
        // File is automatically closed when BufReader is dropped
    }
}

// Example 3: Smart pointer-like wrapper
struct SmartResource<T> {
    resource: Option<T>,
    name: String,
}

impl<T> SmartResource<T> {
    fn new(resource: T, name: String) -> Self {
        println!("Acquiring resource: {}", name);
        SmartResource {
            resource: Some(resource),
            name,
        }
    }

    fn get(&self) -> Option<&T> {
        self.resource.as_ref()
    }

    fn take(mut self) -> Option<T> {
        self.resource.take()
    }
}

impl<T> Drop for SmartResource<T> {
    fn drop(&mut self) {
        if self.resource.is_some() {
            println!("Releasing resource: {}", self.name);
        }
    }
}

// Example 4: Scoped guard pattern
struct ScopeGuard<F: FnOnce()> {
    cleanup: Option<F>,
}

impl<F: FnOnce()> ScopeGuard<F> {
    fn new(cleanup: F) -> Self {
        ScopeGuard {
            cleanup: Some(cleanup),
        }
    }

    fn disarm(mut self) {
        self.cleanup = None;
    }
}

impl<F: FnOnce()> Drop for ScopeGuard<F> {
    fn drop(&mut self) {
        if let Some(cleanup) = self.cleanup.take() {
            cleanup();
        }
    }
}

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    println!("=== RAII/Drop Pattern Examples ===\n");

    // Example 1: Database connection
    {
        println!("1. Database Connection Example:");
        let db = DatabaseConnection::new(1);
        let _results = db.execute_query("SELECT * FROM users")?;
        // db is automatically dropped here, connection closed
        println!();
    }

    // Example 2: File manager
    {
        println!("2. File Manager Example:");
        // Create a temporary file for demonstration
        std::fs::write("temp.txt", "Hello\nWorld\nRust\nRAII")?;

        {
            let mut file_mgr = FileManager::new("temp.txt")?;
            let lines = file_mgr.read_lines()?;
            println!("Read {} lines", lines.len());
            // file_mgr is dropped here, file closed automatically
        }

        // Clean up
        std::fs::remove_file("temp.txt").ok();
        println!();
    }

    // Example 3: Smart resource
    {
        println!("3. Smart Resource Example:");
        let data = vec![1, 2, 3, 4, 5];
        let smart_res = SmartResource::new(data, "Vector Data".to_string());

        if let Some(vec_ref) = smart_res.get() {
            println!("Vector length: {}", vec_ref.len());
        }
        // smart_res is dropped here
        println!();
    }

    // Example 4: Scope guard
    {
        println!("4. Scope Guard Example:");
        let _guard = ScopeGuard::new(|| {
            println!("Cleanup code executed!");
        });

        println!("Doing some work...");
        // guard's cleanup function will be called when _guard is dropped
        println!();
    }

    // Example 5: Early cleanup
    {
        println!("5. Early Cleanup Example:");
        let mut counter = 0;
        let guard = ScopeGuard::new(|| {
            println!("This cleanup will be skipped");
        });

        counter += 1;
        if counter > 0 {
            guard.disarm(); // Prevent cleanup
            println!("Guard disarmed, cleanup won't run");
        }
        println!();
    }

    println!("=== All resources cleaned up automatically ===");
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_database_connection_cleanup() {
        // This test demonstrates that Drop is called even if we don't explicitly close
        let db = DatabaseConnection::new(999);
        assert!(db.is_connected);
        // db will be dropped at end of scope, calling Drop::drop
    }

    #[test]
    fn test_smart_resource_take() {
        let data = "test data".to_string();
        let mut smart_res = SmartResource::new(data, "Test Resource".to_string());

        let taken = smart_res.take();
        assert!(taken.is_some());
        assert_eq!(taken.unwrap(), "test data");

        // Resource was taken, so Drop won't print release message
    }

    #[test]
    fn test_scope_guard_disarm() {
        let mut cleanup_called = false;
        {
            let guard = ScopeGuard::new(|| {
                // This won't be called due to disarm
            });
            guard.disarm();
        }
        // Cleanup should not have been called
    }
}

Key Benefits of RAII/Drop in Rust

  1. Automatic Resource Management: Resources are automatically cleaned up when they go out of scope, eliminating memory leaks and resource leaks.

  2. Exception Safety: Even if a panic occurs, Drop is still called during stack unwinding (unless the panic occurs during unwinding itself).

  3. Zero-Cost Abstraction: The Drop trait adds no runtime overhead - cleanup code is inserted at compile time.

  4. Composability: Types that contain other types with Drop implementations automatically get proper cleanup behavior.

Comparison with Gang of Four Patterns

While RAII/Drop isn't a GoF pattern, it relates to several:

  • Proxy Pattern: Smart pointers like Box<T>, Rc<T>, and Arc<T> use RAII
  • Decorator Pattern: Wrappers that add behavior while maintaining automatic cleanup
  • Template Method Pattern: The Drop trait defines the "template" for cleanup behavior

Best Practices

  1. Always implement Drop for resource-owning types
  2. Use Option<T> to allow early resource release
  3. Consider providing explicit cleanup methods for user control
  4. Use scope guards for complex cleanup scenarios
  5. Be careful with cyclic references in Rc<T> - they won't be cleaned up automatically

The RAII/Drop pattern is fundamental to Rust's memory safety guarantees and makes resource management much more reliable compared to manual cleanup approaches common in other languages.


Core Rust concepts introduced

Building on all previous patterns, the RAII/Drop pattern introduces the final set of core concepts:

1. The Drop Trait and Automatic Cleanup

#![allow(unused)]
fn main() {
impl Drop for DatabaseConnection {
    fn drop(&mut self) {
        if self.is_connected {
            println!("Closing database connection {}", self.connection_id);
            self.is_connected = false;
        }
    }
}
}
  • Drop trait: Defines custom cleanup behavior when values go out of scope
  • Automatic invocation: drop() is called automatically, no manual cleanup needed
  • Deterministic destruction: Cleanup happens at predictable, compile-time-known points
  • Exception safety: Drop runs even during panic unwinding (in most cases)

2. Scope-Based Resource Management (RAII)

#![allow(unused)]
fn main() {
{
    let db = DatabaseConnection::new(1);
    // use db...
} // db.drop() is automatically called here
}
  • Lexical scoping: Resource lifetime tied to variable scope
  • Stack unwinding: Resources cleaned up in reverse order of creation
  • No manual cleanup: Compiler automatically inserts cleanup code
  • Resource acquisition is initialization: Resources acquired in constructors, released in destructors

3. Consuming Methods and Resource Transfer

#![allow(unused)]
fn main() {
fn take(mut self) -> Option<T> {  // Takes ownership of self
    self.resource.take()          // Removes value from Option
}
}
  • Consuming methods: Methods that take self by value
  • Resource transfer: Move resources out of containers
  • Preventing double-cleanup: Taking a resource prevents Drop from running on it
  • Option::take(): Standard way to move values out of Option

4. Advanced Closure Patterns

#![allow(unused)]
fn main() {
struct ScopeGuard<F: FnOnce()> {
    cleanup: Option<F>,
}

let _guard = ScopeGuard::new(|| {
    println!("Cleanup code executed!");
});
}
  • FnOnce() trait bound: Closure that can be called exactly once
  • Closure capture: Functions can capture variables from their environment
  • Scope guards: Pattern for running code when leaving a scope
  • Generic over functions: Types can be parameterized by function types

5. Pattern Matching with Option::take()

#![allow(unused)]
fn main() {
impl<F: FnOnce()> Drop for ScopeGuard<F> {
    fn drop(&mut self) {
        if let Some(cleanup) = self.cleanup.take() {
            cleanup();
        }
    }
}
}
  • Option::take(): Moves value out of Option, leaving None
  • Conditional execution: Only run cleanup if it hasn't been "disarmed"
  • Preventing double-execution: Taking the closure prevents calling it twice
  • if let pattern: Concise way to handle the Some case

6. Disarming Pattern

#![allow(unused)]
fn main() {
fn disarm(mut self) {
    self.cleanup = None;
}
}
  • Manual resource control: Sometimes you want to prevent automatic cleanup
  • Disarming guards: Remove the cleanup function to prevent execution
  • Consuming self: Method takes ownership to prevent further use
  • Explicit control: Provides escape hatch from automatic behavior

7. Unit Types and Zero-Sized Types

#![allow(unused)]
fn main() {
struct FileProcessor;  // Zero-sized type
}
  • Unit types: Types with no data, used for namespacing or marker types
  • Zero runtime cost: These types are completely optimized away
  • Namespacing: Group related functions without creating instances
  • Marker types: Used in type-level programming (phantom types)

8. Generic Type Parameters with Trait Bounds

#![allow(unused)]
fn main() {
struct SmartResource<T> {
    resource: Option<T>,
    name: String,
}

impl<T> SmartResource<T> {  // Implementation for any type T
    fn new(resource: T, name: String) -> Self { ... }
}
}
  • Generic structs: Types parameterized by other types
  • Impl blocks: Implementations work for any type T
  • Monomorphization: Compiler generates specialized code for each concrete type
  • Zero-cost generics: No runtime overhead for generic abstraction

9. Resource Management Patterns

The RAII pattern demonstrates several important resource management concepts:

  • Automatic cleanup: No need to remember to call cleanup functions
  • Exception safety: Cleanup happens even if code panics
  • Composability: Types containing RAII types automatically get proper cleanup
  • Performance: No garbage collection overhead, deterministic timing

10. Testing Automatic Behavior

#![allow(unused)]
fn main() {
#[test]
fn test_database_connection_cleanup() {
    let db = DatabaseConnection::new(999);
    assert!(db.is_connected);
    // db will be dropped at end of scope, calling Drop::drop
}
}
  • Testing destructors: Verify that cleanup code runs correctly
  • Scope-based testing: Let variables go out of scope to trigger Drop
  • Side effect verification: Check that cleanup side effects occurred

These final concepts complete the foundation of Rust's memory management model. The Drop trait and RAII pattern showcase how Rust achieves memory safety and resource safety without garbage collection, making resource management both automatic and predictable.

Chapter 2: Ownership & Borrowing Mastery

This chapter delves deeper into Rust's ownership model and borrowing system through design patterns that showcase advanced memory management techniques. Building on the foundation patterns from Chapter 1, these patterns will teach you to leverage Rust's unique features for efficient, safe, and elegant code. After mastering these patterns, you'll have a deep understanding of how to work with Rust's ownership system rather than fighting against it.

Patterns Covered

Strategy Pattern

Simple trait usage, dynamic dispatch vs static dispatch

Core Rust concepts introduced:

  • Trait Objects and Dynamic Dispatch (dyn Trait)
  • Static vs Dynamic Dispatch Trade-offs
  • Object Safety Rules
  • Vtables and Runtime Polymorphism
  • Box Smart Pointers for Heap Allocation
  • Performance Implications of Dispatch Types

Adapter Pattern

Trait implementations for foreign types, orphan rule

New Rust concepts introduced:

  • The Orphan Rule and Coherence
  • Newtype Wrapper for External Types
  • Foreign Function Interface (FFI) Considerations
  • Trait Implementation Restrictions
  • Upstream/Downstream Crate Relationships
  • Blanket Implementations and Conflicts

Interior Mutability Pattern

When and why to use Cell/RefCell/Mutex

New Rust concepts introduced:

  • Interior Mutability vs Inherited Mutability
  • Cell<T> for Copy Types
  • RefCell<T> for Runtime Borrow Checking
  • Mutex<T> for Thread-Safe Interior Mutability
  • Runtime vs Compile-Time Borrow Checking
  • Shared Ownership with Rc<RefCell<T>>
  • Memory Safety Trade-offs

Cow (Clone on Write)

Efficient memory usage, understanding when cloning is needed

New Rust concepts introduced:

  • Cow<'a, T> Enum and Borrowed/Owned Variants
  • Lazy Cloning and Performance Optimization
  • Lifetime Elision in Return Types
  • ToOwned Trait and Custom Implementations
  • Zero-Copy String Processing
  • API Design for Flexible Input Types
  • Memory Efficiency Patterns

Command Pattern

Closures, function pointers, and ownership of captured variables

New Rust concepts introduced:

  • Closure Traits: Fn, FnMut, and FnOnce
  • Function Pointers (fn) vs Closures
  • Capture Modes: By Reference, By Value, By Move
  • move Keyword for Closure Ownership
  • Boxed Closures for Dynamic Storage
  • Higher-Order Functions and Combinators
  • Callback Patterns and Event Systems

Learning Approach

Each pattern in this chapter builds upon the ownership concepts from Chapter 1 while introducing more sophisticated borrowing and memory management techniques. You'll learn:

  1. When to use different dispatch mechanisms and their performance implications
  2. How to work with external types while respecting Rust's coherence rules
  3. When compile-time borrowing isn't enough and how to safely use runtime alternatives
  4. How to avoid unnecessary cloning while maintaining API flexibility
  5. How to capture and store computation using Rust's powerful closure system

Pattern Structure

Each pattern chapter follows this enhanced structure:

  1. Problem Statement - Real-world scenarios where the pattern applies
  2. Traditional Implementation - How other languages approach the problem
  3. Rust Implementation - Leveraging ownership and borrowing for better solutions
  4. Performance Analysis - Memory and runtime characteristics
  5. Best Practices - When to use each approach and common pitfalls
  6. Advanced Techniques - Pushing the pattern to its limits
  7. Core Rust Concepts Introduced - New ownership and borrowing features

By the end of this chapter, you'll understand Rust's ownership model at a deep level and be able to choose the right ownership patterns for complex scenarios. You'll also be prepared for the concurrent and parallel patterns in later chapters.

Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime. In Rust, this pattern showcases the power of traits for both static and dynamic dispatch, allowing you to choose between compile-time optimization and runtime flexibility.

Problem Statement

You need to switch between different algorithms or behaviors at runtime based on configuration, user input, or changing conditions. Traditional object-oriented languages use inheritance or interfaces, but Rust's trait system provides more powerful and efficient alternatives.

Traditional Object-Oriented Implementation

In Java, the Strategy pattern typically uses interfaces and classes:

// Strategy interface
interface PaymentStrategy {
    void pay(double amount);
}

// Concrete strategies
class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " using credit card: " + cardNumber);
    }
}

class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " using PayPal: " + email);
    }
}

// Context class
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(double amount) {
        paymentStrategy.pay(amount);
    }
}

Rust Implementation

Rust provides multiple ways to implement the Strategy pattern, each with different trade-offs:

1. Static Dispatch with Generics

#![allow(unused)]
fn main() {
// Strategy trait
trait PaymentStrategy {
    fn pay(&self, amount: f64);
}

// Concrete strategies
struct CreditCardPayment {
    card_number: String,
}

impl CreditCardPayment {
    fn new(card_number: String) -> Self {
        Self { card_number }
    }
}

impl PaymentStrategy for CreditCardPayment {
    fn pay(&self, amount: f64) {
        println!("Paid ${:.2} using credit card: {}", amount, self.card_number);
    }
}

struct PayPalPayment {
    email: String,
}

impl PayPalPayment {
    fn new(email: String) -> Self {
        Self { email }
    }
}

impl PaymentStrategy for PayPalPayment {
    fn pay(&self, amount: f64) {
        println!("Paid ${:.2} using PayPal: {}", amount, self.email);
    }
}

// Context with static dispatch
struct ShoppingCart<T: PaymentStrategy> {
    payment_strategy: T,
    items: Vec<(String, f64)>,
}

impl<T: PaymentStrategy> ShoppingCart<T> {
    fn new(payment_strategy: T) -> Self {
        Self {
            payment_strategy,
            items: Vec::new(),
        }
    }

    fn add_item(&mut self, name: String, price: f64) {
        self.items.push((name, price));
    }

    fn checkout(&self) {
        let total: f64 = self.items.iter().map(|(_, price)| price).sum();
        self.payment_strategy.pay(total);
    }
}
}

2. Dynamic Dispatch with Trait Objects

#![allow(unused)]
fn main() {
// Using Box<dyn Trait> for owned trait objects
struct DynamicShoppingCart {
    payment_strategy: Box<dyn PaymentStrategy>,
    items: Vec<(String, f64)>,
}

impl DynamicShoppingCart {
    fn new(payment_strategy: Box<dyn PaymentStrategy>) -> Self {
        Self {
            payment_strategy,
            items: Vec::new(),
        }
    }

    fn set_payment_strategy(&mut self, strategy: Box<dyn PaymentStrategy>) {
        self.payment_strategy = strategy;
    }

    fn add_item(&mut self, name: String, price: f64) {
        self.items.push((name, price));
    }

    fn checkout(&self) {
        let total: f64 = self.items.iter().map(|(_, price)| price).sum();
        self.payment_strategy.pay(total);
    }
}

// Alternative: Using reference to trait object
fn process_payment(strategy: &dyn PaymentStrategy, amount: f64) {
    strategy.pay(amount);
}
}

3. Enum-Based Strategy (Rust Idiomatic)

#![allow(unused)]
fn main() {
// Enum-based approach - often more idiomatic in Rust
#[derive(Debug, Clone)]
enum PaymentMethod {
    CreditCard { card_number: String },
    PayPal { email: String },
    BankTransfer { account_number: String },
    Cryptocurrency { wallet_address: String },
}

impl PaymentMethod {
    fn pay(&self, amount: f64) {
        match self {
            PaymentMethod::CreditCard { card_number } => {
                println!("Paid ${:.2} using credit card: {}", amount, card_number);
            }
            PaymentMethod::PayPal { email } => {
                println!("Paid ${:.2} using PayPal: {}", amount, email);
            }
            PaymentMethod::BankTransfer { account_number } => {
                println!("Paid ${:.2} using bank transfer: {}", amount, account_number);
            }
            PaymentMethod::Cryptocurrency { wallet_address } => {
                println!("Paid ${:.2} using crypto wallet: {}", amount, wallet_address);
            }
        }
    }
}

struct ModernShoppingCart {
    payment_method: PaymentMethod,
    items: Vec<(String, f64)>,
}

impl ModernShoppingCart {
    fn new(payment_method: PaymentMethod) -> Self {
        Self {
            payment_method,
            items: Vec::new(),
        }
    }

    fn set_payment_method(&mut self, method: PaymentMethod) {
        self.payment_method = method;
    }

    fn add_item(&mut self, name: String, price: f64) {
        self.items.push((name, price));
    }

    fn checkout(&self) {
        let total: f64 = self.items.iter().map(|(_, price)| price).sum();
        self.payment_method.pay(total);
    }
}
}

4. Advanced: Function Pointer Strategy

#![allow(unused)]
fn main() {
// Using function pointers for strategy
type PaymentFunction = fn(f64, &str);

fn credit_card_payment(amount: f64, details: &str) {
    println!("Paid ${:.2} using credit card: {}", amount, details);
}

fn paypal_payment(amount: f64, details: &str) {
    println!("Paid ${:.2} using PayPal: {}", amount, details);
}

struct FunctionShoppingCart {
    payment_fn: PaymentFunction,
    payment_details: String,
    items: Vec<(String, f64)>,
}

impl FunctionShoppingCart {
    fn new(payment_fn: PaymentFunction, payment_details: String) -> Self {
        Self {
            payment_fn,
            payment_details,
            items: Vec::new(),
        }
    }

    fn checkout(&self) {
        let total: f64 = self.items.iter().map(|(_, price)| price).sum();
        (self.payment_fn)(total, &self.payment_details);
    }
}
}

Complete Example and Usage

fn main() {
    println!("=== Strategy Pattern Examples ===\n");

    // 1. Static dispatch example
    println!("1. Static Dispatch:");
    let mut static_cart = ShoppingCart::new(CreditCardPayment::new("1234-5678-9012-3456".to_string()));
    static_cart.add_item("Laptop".to_string(), 999.99);
    static_cart.add_item("Mouse".to_string(), 29.99);
    static_cart.checkout();

    // 2. Dynamic dispatch example
    println!("\n2. Dynamic Dispatch:");
    let mut dynamic_cart = DynamicShoppingCart::new(
        Box::new(PayPalPayment::new("user@example.com".to_string()))
    );
    dynamic_cart.add_item("Keyboard".to_string(), 79.99);
    dynamic_cart.checkout();

    // Switch strategy at runtime
    dynamic_cart.set_payment_strategy(
        Box::new(CreditCardPayment::new("9876-5432-1098-7654".to_string()))
    );
    dynamic_cart.checkout();

    // 3. Enum-based approach
    println!("\n3. Enum-based Strategy:");
    let mut modern_cart = ModernShoppingCart::new(
        PaymentMethod::Cryptocurrency {
            wallet_address: "1A2B3C4D5E6F7G8H9I0J".to_string()
        }
    );
    modern_cart.add_item("Graphics Card".to_string(), 599.99);
    modern_cart.checkout();

    // 4. Function pointer approach
    println!("\n4. Function Pointer Strategy:");
    let mut fn_cart = FunctionShoppingCart::new(
        credit_card_payment,
        "4444-3333-2222-1111".to_string()
    );
    fn_cart.items.push(("Monitor".to_string(), 299.99));
    fn_cart.checkout();

    // 5. Demonstrating runtime strategy selection
    println!("\n5. Runtime Strategy Selection:");
    demonstrate_runtime_selection();
}

fn demonstrate_runtime_selection() {
    use std::io;

    println!("Choose payment method:");
    println!("1. Credit Card");
    println!("2. PayPal");
    println!("3. Bank Transfer");

    // Simulate user input (in real code, you'd read from stdin)
    let choice = 2; // Simulated input

    let payment_method = match choice {
        1 => PaymentMethod::CreditCard {
            card_number: "1111-2222-3333-4444".to_string()
        },
        2 => PaymentMethod::PayPal {
            email: "customer@email.com".to_string()
        },
        3 => PaymentMethod::BankTransfer {
            account_number: "ACC123456789".to_string()
        },
        _ => PaymentMethod::CreditCard {
            card_number: "DEFAULT-CARD".to_string()
        },
    };

    let mut cart = ModernShoppingCart::new(payment_method);
    cart.add_item("Book".to_string(), 19.99);
    cart.checkout();
}

Performance Analysis

Static Dispatch (Generics)

  • Compile-time optimization: Zero runtime cost for method calls
  • Code size: May increase due to monomorphization
  • Flexibility: Strategy must be known at compile time

Dynamic Dispatch (Trait Objects)

  • Runtime flexibility: Can switch strategies at runtime
  • Vtable overhead: Small runtime cost for virtual method calls
  • Memory: Additional pointer indirection

Enum-Based Strategy

  • Best of both worlds: Pattern matching is optimized by compiler
  • Memory efficient: No heap allocation or vtables
  • Type safety: All variants known at compile time

Best Practices

  1. Use enums for closed sets of strategies known at compile time
  2. Use trait objects for open sets or plugin architectures
  3. Use generics for maximum performance when strategy is known at compile time
  4. Consider function pointers for simple strategies without state

When to Use Each Approach

  • Enums: When you control all strategies and they're known at compile time
  • Trait objects: When you need runtime flexibility or plugin systems
  • Generics: When performance is critical and strategy is compile-time known
  • Function pointers: For simple stateless strategies

Core Rust concepts introduced

Building on the foundation patterns, the Strategy pattern introduces several new concepts related to dispatch and polymorphism:

1. Trait Objects and Dynamic Dispatch

#![allow(unused)]
fn main() {
Box<dyn PaymentStrategy>  // Trait object
&dyn PaymentStrategy      // Reference to trait object
}
  • Trait objects: Allow runtime polymorphism through vtables
  • dyn keyword: Explicitly marks dynamic dispatch
  • Object safety: Not all traits can be made into trait objects
  • Vtable overhead: Small runtime cost for virtual method calls

2. Static vs Dynamic Dispatch Trade-offs

#![allow(unused)]
fn main() {
// Static dispatch - zero runtime cost
fn process_static<T: PaymentStrategy>(strategy: &T, amount: f64) {
    strategy.pay(amount);  // Compile-time method resolution
}

// Dynamic dispatch - runtime flexibility
fn process_dynamic(strategy: &dyn PaymentStrategy, amount: f64) {
    strategy.pay(amount);  // Runtime method lookup via vtable
}
}
  • Monomorphization: Compiler generates separate code for each concrete type
  • Code bloat vs performance: Static dispatch can increase binary size
  • Runtime flexibility: Dynamic dispatch allows changing behavior at runtime

3. Object Safety Rules

#![allow(unused)]
fn main() {
trait ObjectSafe {
    fn method(&self);  // ✅ Object safe
}

trait NotObjectSafe {
    fn generic_method<T>(&self);  // ❌ Not object safe
    fn static_method();           // ❌ Not object safe
}
}
  • Object safety requirements: Methods must be callable through trait objects
  • No generic methods: Generics prevent trait object creation
  • No associated functions: Static methods can't be called on trait objects
  • Self limitations: Self must be behind a pointer

4. Box Smart Pointers for Heap Allocation

#![allow(unused)]
fn main() {
Box<dyn PaymentStrategy>  // Owned trait object on heap
}
  • Heap allocation: Box<T> allocates T on the heap
  • Owned data: Box<T> owns its data, unlike references
  • Deref coercion: Box<T> automatically derefs to T
  • Zero-cost abstraction: No runtime overhead beyond the allocation

5. Performance Implications of Dispatch Types

Static dispatch characteristics:

  • Zero runtime overhead for method calls
  • Potential code size increase due to monomorphization
  • All optimizations possible (inlining, etc.)
  • Strategy must be known at compile time

Dynamic dispatch characteristics:

  • Small vtable lookup overhead
  • Consistent code size regardless of number of implementers
  • Limited optimization opportunities
  • Full runtime flexibility

6. Vtables and Runtime Polymorphism

#![allow(unused)]
fn main() {
// Under the hood, trait objects use vtables
struct TraitObject {
    data: *mut (),           // Pointer to the actual data
    vtable: *const VTable,   // Pointer to method implementations
}
}
  • Vtable structure: Contains pointers to trait method implementations
  • Double indirection: Data pointer + vtable pointer
  • Method resolution: Runtime lookup in vtable for correct implementation
  • Memory layout: Understanding the cost of dynamic dispatch

These concepts demonstrate how Rust provides both zero-cost abstractions (static dispatch) and runtime flexibility (dynamic dispatch), allowing you to choose the right trade-off for each situation. The Strategy pattern showcases when and how to use each approach effectively.

Adapter Pattern

The Adapter pattern allows incompatible interfaces to work together by providing a wrapper that translates one interface to another. In Rust, this pattern highlights the orphan rule, coherence principles, and techniques for integrating external types into your type system.

Problem Statement

You need to use a type from an external crate (library) but want to implement a trait for it, or you need to adapt an existing interface to work with your code. Rust's orphan rule prevents implementing external traits for external types, so you need alternative approaches.

Traditional Object-Oriented Implementation

In Java, the Adapter pattern typically wraps an incompatible class:

// Target interface that our code expects
interface MediaPlayer {
    void play(String audioType, String fileName);
}

// External library interface (incompatible)
interface AdvancedMediaPlayer {
    void playVlc(String fileName);
    void playMp4(String fileName);
}

// External library implementation
class VlcPlayer implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        System.out.println("Playing vlc file: " + fileName);
    }

    @Override
    public void playMp4(String fileName) {
        // Do nothing - VLC player can't play MP4
    }
}

class Mp4Player implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        // Do nothing - MP4 player can't play VLC
    }

    @Override
    public void playMp4(String fileName) {
        System.out.println("Playing mp4 file: " + fileName);
    }
}

// Adapter that makes external library compatible
class MediaAdapter implements MediaPlayer {
    AdvancedMediaPlayer advancedPlayer;

    public MediaAdapter(String audioType) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedPlayer = new VlcPlayer();
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedPlayer = new Mp4Player();
        }
    }

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedPlayer.playVlc(fileName);
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedPlayer.playMp4(fileName);
        }
    }
}

Understanding the Orphan Rule

Rust's orphan rule (also called the coherence rule) states that you can only implement a trait for a type if you own either the trait or the type. This prevents conflicts but requires adapter patterns:

#![allow(unused)]
fn main() {
// ❌ This won't compile - orphan rule violation
// Can't implement external trait for external type
impl std::fmt::Display for std::fs::File {
    // Error: neither trait nor type is local to this crate
}

// ❌ This also won't compile
// Can't implement external trait for external generic
impl<T> std::clone::Clone for Vec<T> {
    // Error: Vec is from std, Clone is from std
}
}

Rust Implementation

1. NewType Wrapper Pattern

The most common way to adapt external types:

#![allow(unused)]
fn main() {
use std::collections::HashMap;

// External type we want to adapt
type ExternalConfig = HashMap<String, String>;

// Our desired trait
trait Configurable {
    fn get_setting(&self, key: &str) -> Option<&str>;
    fn set_setting(&mut self, key: String, value: String);
    fn has_setting(&self, key: &str) -> bool;
}

// NewType wrapper to overcome orphan rule
#[derive(Debug, Clone)]
struct Config(ExternalConfig);

impl Config {
    fn new() -> Self {
        Config(HashMap::new())
    }

    fn from_external(external: ExternalConfig) -> Self {
        Config(external)
    }

    fn into_external(self) -> ExternalConfig {
        self.0
    }
}

// Now we can implement our trait for our wrapper
impl Configurable for Config {
    fn get_setting(&self, key: &str) -> Option<&str> {
        self.0.get(key).map(String::as_str)
    }

    fn set_setting(&mut self, key: String, value: String) {
        self.0.insert(key, value);
    }

    fn has_setting(&self, key: &str) -> bool {
        self.0.contains_key(key)
    }
}

// Implement Deref for convenient access to underlying type
impl std::ops::Deref for Config {
    type Target = ExternalConfig;

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

impl std::ops::DerefMut for Config {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}
}

2. Adapter Struct Pattern

When you need to adapt between different interfaces:

#![allow(unused)]
fn main() {
// Simulating external media library types
struct VlcPlayer {
    volume: u8,
}

impl VlcPlayer {
    fn new() -> Self {
        VlcPlayer { volume: 50 }
    }

    fn play_vlc_file(&self, filename: &str) {
        println!("VLC: Playing {} at volume {}", filename, self.volume);
    }

    fn set_volume(&mut self, vol: u8) {
        self.volume = vol;
    }
}

struct Mp4Player {
    quality: String,
}

impl Mp4Player {
    fn new() -> Self {
        Mp4Player {
            quality: "HD".to_string()
        }
    }

    fn play_mp4_stream(&self, filename: &str) {
        println!("MP4: Streaming {} in {} quality", filename, self.quality);
    }

    fn set_quality(&mut self, quality: String) {
        self.quality = quality;
    }
}

// Our unified interface
trait MediaPlayer {
    fn play(&self, filename: &str);
    fn get_info(&self) -> String;
}

// Adapter for VLC player
struct VlcAdapter {
    player: VlcPlayer,
}

impl VlcAdapter {
    fn new() -> Self {
        VlcAdapter {
            player: VlcPlayer::new(),
        }
    }

    fn set_volume(&mut self, volume: u8) {
        self.player.set_volume(volume);
    }
}

impl MediaPlayer for VlcAdapter {
    fn play(&self, filename: &str) {
        // Adapt the interface
        self.player.play_vlc_file(filename);
    }

    fn get_info(&self) -> String {
        format!("VLC Player (Volume: {})", self.player.volume)
    }
}

// Adapter for MP4 player
struct Mp4Adapter {
    player: Mp4Player,
}

impl Mp4Adapter {
    fn new() -> Self {
        Mp4Adapter {
            player: Mp4Player::new(),
        }
    }

    fn set_quality(&mut self, quality: String) {
        self.player.set_quality(quality);
    }
}

impl MediaPlayer for Mp4Adapter {
    fn play(&self, filename: &str) {
        // Adapt the interface
        self.player.play_mp4_stream(filename);
    }

    fn get_info(&self) -> String {
        format!("MP4 Player (Quality: {})", self.player.quality)
    }
}
}

3. Generic Adapter Pattern

For more flexible adaptations:

#![allow(unused)]
fn main() {
// External library simulation - temperature sensors
trait TemperatureSensor {
    fn read_celsius(&self) -> f64;
}

struct AnalogSensor {
    pin: u8,
    calibration: f64,
}

impl AnalogSensor {
    fn new(pin: u8) -> Self {
        AnalogSensor {
            pin,
            calibration: 1.0
        }
    }

    fn read_voltage(&self) -> f64 {
        // Simulate reading from analog pin
        3.3 * (self.pin as f64 / 255.0) + self.calibration
    }
}

struct DigitalSensor {
    address: u8,
}

impl DigitalSensor {
    fn new(address: u8) -> Self {
        DigitalSensor { address }
    }

    fn read_raw_data(&self) -> u16 {
        // Simulate I2C communication
        1024 + (self.address as u16 * 10)
    }
}

// Generic adapter that can adapt any type
struct SensorAdapter<T> {
    sensor: T,
    conversion_fn: fn(&T) -> f64,
}

impl<T> SensorAdapter<T> {
    fn new(sensor: T, conversion_fn: fn(&T) -> f64) -> Self {
        SensorAdapter {
            sensor,
            conversion_fn,
        }
    }
}

impl<T> TemperatureSensor for SensorAdapter<T> {
    fn read_celsius(&self) -> f64 {
        (self.conversion_fn)(&self.sensor)
    }
}

// Conversion functions
fn analog_to_celsius(sensor: &AnalogSensor) -> f64 {
    let voltage = sensor.read_voltage();
    // Convert voltage to temperature (example conversion)
    (voltage - 0.5) * 100.0
}

fn digital_to_celsius(sensor: &DigitalSensor) -> f64 {
    let raw = sensor.read_raw_data();
    // Convert raw digital value to temperature
    (raw as f64 - 1024.0) / 10.0
}
}

4. Trait Object Adapter

For runtime adaptation:

#![allow(unused)]
fn main() {
// Unified media player using trait objects
struct UnifiedMediaPlayer {
    players: Vec<Box<dyn MediaPlayer>>,
    current_player: usize,
}

impl UnifiedMediaPlayer {
    fn new() -> Self {
        UnifiedMediaPlayer {
            players: Vec::new(),
            current_player: 0,
        }
    }

    fn add_player(&mut self, player: Box<dyn MediaPlayer>) {
        self.players.push(player);
    }

    fn switch_player(&mut self, index: usize) -> Result<(), String> {
        if index < self.players.len() {
            self.current_player = index;
            Ok(())
        } else {
            Err("Player index out of bounds".to_string())
        }
    }

    fn play(&self, filename: &str) -> Result<(), String> {
        if let Some(player) = self.players.get(self.current_player) {
            player.play(filename);
            Ok(())
        } else {
            Err("No player available".to_string())
        }
    }

    fn list_players(&self) -> Vec<String> {
        self.players.iter().map(|p| p.get_info()).collect()
    }
}
}

Complete Example and Usage

fn main() {
    println!("=== Adapter Pattern Examples ===\n");

    // 1. NewType wrapper example
    println!("1. NewType Wrapper Adapter:");
    let mut config = Config::new();
    config.set_setting("database_url".to_string(), "localhost:5432".to_string());
    config.set_setting("max_connections".to_string(), "100".to_string());

    println!("Database URL: {:?}", config.get_setting("database_url"));
    println!("Has timeout setting: {}", config.has_setting("timeout"));

    // Access underlying HashMap through Deref
    println!("All settings: {:?}", &*config);

    // 2. Adapter struct example
    println!("\n2. Media Player Adapters:");
    let vlc = VlcAdapter::new();
    let mp4 = Mp4Adapter::new();

    vlc.play("movie.vlc");
    mp4.play("video.mp4");

    println!("Players: {} | {}", vlc.get_info(), mp4.get_info());

    // 3. Generic adapter example
    println!("\n3. Generic Sensor Adapters:");
    let analog = AnalogSensor::new(128);
    let digital = DigitalSensor::new(0x48);

    let analog_adapter = SensorAdapter::new(analog, analog_to_celsius);
    let digital_adapter = SensorAdapter::new(digital, digital_to_celsius);

    println!("Analog sensor: {:.2}°C", analog_adapter.read_celsius());
    println!("Digital sensor: {:.2}°C", digital_adapter.read_celsius());

    // 4. Trait object adapter example
    println!("\n4. Unified Media Player:");
    let mut unified = UnifiedMediaPlayer::new();
    unified.add_player(Box::new(VlcAdapter::new()));
    unified.add_player(Box::new(Mp4Adapter::new()));

    println!("Available players: {:?}", unified.list_players());

    unified.play("test.vlc").unwrap();
    unified.switch_player(1).unwrap();
    unified.play("test.mp4").unwrap();

    // 5. Demonstrating orphan rule compliance
    println!("\n5. Working with External Types:");
    demonstrate_external_types();
}

fn demonstrate_external_types() {
    use std::path::PathBuf;

    // We can't implement external traits for external types,
    // but we can wrap them
    #[derive(Debug)]
    struct PathAdapter(PathBuf);

    impl PathAdapter {
        fn new<P: Into<PathBuf>>(path: P) -> Self {
            PathAdapter(path.into())
        }
    }

    // Now we can implement our own traits
    trait PathInfo {
        fn is_source_file(&self) -> bool;
        fn get_project_relative_path(&self) -> String;
    }

    impl PathInfo for PathAdapter {
        fn is_source_file(&self) -> bool {
            self.0.extension()
                .and_then(|ext| ext.to_str())
                .map(|ext| matches!(ext, "rs" | "toml" | "md"))
                .unwrap_or(false)
        }

        fn get_project_relative_path(&self) -> String {
            // Simplified - in real code you'd handle this properly
            self.0.to_string_lossy().to_string()
        }
    }

    let path = PathAdapter::new("src/main.rs");
    println!("Is source file: {}", path.is_source_file());
    println!("Relative path: {}", path.get_project_relative_path());
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_config_adapter() {
        let mut config = Config::new();
        config.set_setting("key".to_string(), "value".to_string());

        assert_eq!(config.get_setting("key"), Some("value"));
        assert!(config.has_setting("key"));
        assert!(!config.has_setting("nonexistent"));
    }

    #[test]
    fn test_media_adapters() {
        let vlc = VlcAdapter::new();
        let mp4 = Mp4Adapter::new();

        // Just test that the interface works
        vlc.play("test.vlc");
        mp4.play("test.mp4");

        assert!(vlc.get_info().contains("VLC"));
        assert!(mp4.get_info().contains("MP4"));
    }

    #[test]
    fn test_generic_adapter() {
        let sensor = AnalogSensor::new(100);
        let adapter = SensorAdapter::new(sensor, analog_to_celsius);

        let temp = adapter.read_celsius();
        assert!(temp > -273.0); // Above absolute zero
    }
}

Orphan Rule Deep Dive

The orphan rule exists to ensure coherence - the property that there's only one implementation of a trait for any given type. This prevents conflicts when multiple crates try to implement the same trait for the same type.

What the Orphan Rule Allows

#![allow(unused)]
fn main() {
// ✅ Local trait, external type
trait MyTrait {
    fn my_method(&self);
}

impl MyTrait for String {
    fn my_method(&self) {
        println!("My implementation for String");
    }
}

// ✅ External trait, local type
struct MyType;

impl std::fmt::Display for MyType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "MyType")
    }
}

// ✅ Local trait, local type
impl MyTrait for MyType {
    fn my_method(&self) {
        println!("My implementation for MyType");
    }
}
}

What the Orphan Rule Prevents

#![allow(unused)]
fn main() {
// ❌ External trait, external type
impl std::fmt::Display for std::fs::File {
    // Error: can't implement external trait for external type
}

// ❌ Blanket implementation conflict
impl<T> std::fmt::Display for Vec<T> {
    // Error: would conflict with potential upstream implementations
}
}

Best Practices

  1. Use NewType wrappers for external types you need to extend
  2. Implement Deref and DerefMut for transparent access to wrapped types
  3. Consider generic adapters for families of similar types
  4. Use composition over inheritance - wrap rather than extend
  5. Document the adaptation clearly for maintainers

When to Use the Adapter Pattern

  • Integrating external libraries with incompatible interfaces
  • Gradually migrating from one interface to another
  • Creating facades for complex external APIs
  • Adding functionality to types you don't own
  • Ensuring interface consistency across different implementations

Core Rust concepts introduced

Building on previous patterns, the Adapter pattern introduces several new concepts related to Rust's coherence rules and type system integration:

1. The Orphan Rule and Coherence

#![allow(unused)]
fn main() {
// ❌ Orphan rule violation
impl std::fmt::Display for std::fs::File {
    // Error: neither trait nor type is local to this crate
}

// ✅ Orphan rule compliance - wrap external type
struct FileAdapter(std::fs::File);
impl std::fmt::Display for FileAdapter {
    // OK: local type implementing external trait
}
}
  • Coherence principle: Ensures only one implementation of a trait for any type
  • Orphan rule: Can only implement trait for type if you own either the trait or the type
  • Conflict prevention: Prevents diamond problem and implementation conflicts
  • Upstream/downstream safety: Protects against breaking changes from dependencies

2. NewType Wrapper for External Types

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
struct Config(HashMap<String, String>);  // Wrapper around external type

impl Deref for Config {
    type Target = HashMap<String, String>;
    fn deref(&self) -> &Self::Target { &self.0 }
}
}
  • Single-field tuple struct: Wraps external type in local type
  • Zero-cost abstraction: Wrapper has no runtime overhead
  • Deref coercion: Provides transparent access to wrapped type
  • API control: You choose which methods to expose

3. Foreign Function Interface (FFI) Considerations

#![allow(unused)]
fn main() {
// When adapting C libraries or external bindings
#[repr(C)]
struct CStruct {
    field: i32,
}

struct SafeWrapper(CStruct);

impl SafeWrapper {
    fn new(value: i32) -> Self {
        SafeWrapper(CStruct { field: value })
    }

    // Provide safe interface to unsafe operations
    fn get_value(&self) -> i32 {
        self.0.field
    }
}
}
  • Memory layout control: #[repr(C)] for C compatibility
  • Safety boundary: Wrapper provides safe interface to unsafe operations
  • Resource management: Wrapper can handle cleanup of external resources
  • Type safety: Convert between Rust and external type systems

4. Trait Implementation Restrictions

#![allow(unused)]
fn main() {
// These restrictions exist due to coherence rules:

// ❌ Can't add blanket implementations that might conflict
impl<T> Clone for Vec<T> {
    // Error: might conflict with std's implementation
}

// ✅ Can implement for specific types you control
impl Clone for MyWrapper<SomeType> {
    // OK: MyWrapper is local to this crate
}
}
  • Blanket implementation conflicts: Generic implementations can create conflicts
  • Specialization limitations: Rust prevents overlapping implementations
  • Future compatibility: Rules protect against breaking changes in dependencies
  • Explicit is better: Forces explicit wrapper types for clarity

5. Upstream/Downstream Crate Relationships

#![allow(unused)]
fn main() {
// Understanding crate dependencies for orphan rule

// In your crate (downstream from std):
use std::collections::HashMap;

// ❌ Can't implement std trait for std type
impl std::fmt::Display for HashMap<String, String> {
    // Error: both trait and type are upstream
}

// ✅ Can wrap and implement
struct DisplayableMap(HashMap<String, String>);
impl std::fmt::Display for DisplayableMap {
    // OK: local type, external trait
}
}
  • Upstream crates: Dependencies your crate relies on
  • Downstream crates: Crates that depend on your crate
  • Orphan rule direction: Protects upstream crates from downstream changes
  • Semantic versioning: Supports SemVer by preventing breaking changes

6. Blanket Implementations and Conflicts

#![allow(unused)]
fn main() {
// Understanding how blanket implementations create restrictions

// In std library:
impl<T: Clone> Clone for Vec<T> {
    // This prevents you from implementing Clone for Vec<YourType>
}

// Your code:
// ❌ This would conflict with std's blanket implementation
impl Clone for Vec<MyType> {
    // Error: conflicting implementation
}

// ✅ Use newtype to avoid conflict
struct MyVec<T>(Vec<T>);
impl<T: Clone> Clone for MyVec<T> {
    // OK: different type
}
}
  • Blanket implementations: Generic implementations that cover many types
  • Conflict detection: Rust prevents potentially overlapping implementations
  • Coherence checking: Compiler ensures only one implementation path exists
  • Workaround patterns: NewType is the standard solution

These concepts demonstrate how Rust's type system maintains safety and predictability while providing escape hatches through adapter patterns. The orphan rule might seem restrictive, but it prevents entire classes of bugs and versioning problems common in other languages.

Interior Mutability Pattern

Interior mutability is a design pattern in Rust that allows you to mutate data even when there are immutable references to it. This pattern is essential for scenarios where the borrow checker's compile-time restrictions are too conservative, but you can guarantee safety through runtime checks.

Problem Statement

Sometimes you need to mutate data through shared references, or you have complex borrowing patterns that the borrow checker can't statically verify as safe. Traditional Rust borrowing requires exclusive access for mutation, but certain patterns require mutation through shared ownership.

Understanding Inherited vs Interior Mutability

#![allow(unused)]
fn main() {
// Inherited mutability - mutability "inherited" from binding
let mut x = 5;
x = 10; // ✅ OK - x is mutable

let y = 5;
// y = 10; // ❌ Error - y is immutable

// Interior mutability - mutation through immutable references
use std::cell::Cell;
let x = Cell::new(5);  // x itself is immutable
x.set(10);             // ✅ OK - interior mutability allows this
}

When Interior Mutability is Needed

  1. Shared ownership with mutation - Multiple owners need to modify data
  2. Caching and memoization - Immutable interface with internal optimization
  3. Observer patterns - Notifying observers requires mutation
  4. Circular references - Breaking cycles while maintaining mutability
  5. C FFI - Interfacing with C code that expects mutable data

Types of Interior Mutability

1. Cell<T> - For Copy Types

Cell<T> provides interior mutability for Copy types through value replacement:

#![allow(unused)]
fn main() {
use std::cell::Cell;

struct Counter {
    value: Cell<u32>,
    name: String,
}

impl Counter {
    fn new(name: String) -> Self {
        Counter {
            value: Cell::new(0),
            name,
        }
    }

    // Can increment through immutable reference
    fn increment(&self) {
        let current = self.value.get();
        self.value.set(current + 1);
    }

    fn get(&self) -> u32 {
        self.value.get()
    }

    fn reset(&self) {
        self.value.set(0);
    }
}

fn demonstrate_cell() {
    let counter = Counter::new("events".to_string());

    // All these work through immutable references
    counter.increment();
    counter.increment();
    println!("{}: {}", counter.name, counter.get()); // events: 2

    // Multiple immutable references can all mutate
    let counter_ref1 = &counter;
    let counter_ref2 = &counter;

    counter_ref1.increment();
    counter_ref2.increment();
    println!("{}: {}", counter.name, counter.get()); // events: 4
}
}

2. RefCell<T> - For Runtime Borrow Checking

RefCell<T> provides interior mutability for any type through runtime borrow checking:

#![allow(unused)]
fn main() {
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
    parent: RefCell<Option<Rc<Node>>>,
}

impl Node {
    fn new(value: i32) -> Rc<Self> {
        Rc::new(Node {
            value,
            children: RefCell::new(Vec::new()),
            parent: RefCell::new(None),
        })
    }

    fn add_child(self: &Rc<Self>, child: Rc<Node>) {
        // Mutate through shared reference
        self.children.borrow_mut().push(child.clone());
        *child.parent.borrow_mut() = Some(self.clone());
    }

    fn remove_child(self: &Rc<Self>, child_value: i32) -> bool {
        let mut children = self.children.borrow_mut();
        if let Some(pos) = children.iter().position(|child| child.value == child_value) {
            let removed = children.remove(pos);
            *removed.parent.borrow_mut() = None;
            true
        } else {
            false
        }
    }

    fn get_children_values(&self) -> Vec<i32> {
        self.children.borrow().iter().map(|child| child.value).collect()
    }
}

fn demonstrate_refcell() {
    let root = Node::new(1);
    let child1 = Node::new(2);
    let child2 = Node::new(3);

    root.add_child(child1);
    root.add_child(child2);

    println!("Root children: {:?}", root.get_children_values()); // [2, 3]

    root.remove_child(2);
    println!("After removal: {:?}", root.get_children_values()); // [3]
}
}

3. Mutex<T> - For Thread-Safe Interior Mutability

Mutex<T> provides thread-safe interior mutability:

#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

struct SharedCounter {
    value: Mutex<u32>,
    name: String,
}

impl SharedCounter {
    fn new(name: String) -> Arc<Self> {
        Arc::new(SharedCounter {
            value: Mutex::new(0),
            name,
        })
    }

    fn increment(&self) -> Result<(), String> {
        match self.value.lock() {
            Ok(mut guard) => {
                *guard += 1;
                Ok(())
            }
            Err(_) => Err("Mutex poisoned".to_string()),
        }
    }

    fn get(&self) -> Result<u32, String> {
        match self.value.lock() {
            Ok(guard) => Ok(*guard),
            Err(_) => Err("Mutex poisoned".to_string()),
        }
    }

    fn add(&self, amount: u32) -> Result<(), String> {
        match self.value.lock() {
            Ok(mut guard) => {
                *guard += amount;
                Ok(())
            }
            Err(_) => Err("Mutex poisoned".to_string()),
        }
    }
}

fn demonstrate_mutex() {
    let counter = SharedCounter::new("thread_safe".to_string());
    let mut handles = vec![];

    // Spawn multiple threads that all increment the counter
    for i in 0..5 {
        let counter_clone = counter.clone();
        let handle = thread::spawn(move || {
            for _ in 0..10 {
                counter_clone.increment().unwrap();
                thread::sleep(Duration::from_millis(1));
            }
            println!("Thread {} finished", i);
        });
        handles.push(handle);
    }

    // Wait for all threads to complete
    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final count: {}", counter.get().unwrap()); // Should be 50
}
}

4. RwLock<T> - For Reader-Writer Patterns

RwLock<T> allows multiple readers or one writer:

#![allow(unused)]
fn main() {
use std::sync::{Arc, RwLock};
use std::thread;
use std::collections::HashMap;

struct SharedCache {
    data: RwLock<HashMap<String, String>>,
}

impl SharedCache {
    fn new() -> Arc<Self> {
        Arc::new(SharedCache {
            data: RwLock::new(HashMap::new()),
        })
    }

    fn get(&self, key: &str) -> Option<String> {
        // Multiple readers can access simultaneously
        self.data.read().unwrap().get(key).cloned()
    }

    fn insert(&self, key: String, value: String) {
        // Exclusive writer access
        self.data.write().unwrap().insert(key, value);
    }

    fn remove(&self, key: &str) -> Option<String> {
        self.data.write().unwrap().remove(key)
    }

    fn len(&self) -> usize {
        self.data.read().unwrap().len()
    }
}

fn demonstrate_rwlock() {
    let cache = SharedCache::new();

    // Insert some initial data
    cache.insert("user:1".to_string(), "Alice".to_string());
    cache.insert("user:2".to_string(), "Bob".to_string());

    let mut handles = vec![];

    // Spawn reader threads
    for i in 0..3 {
        let cache_clone = cache.clone();
        let handle = thread::spawn(move || {
            for j in 0..5 {
                let key = format!("user:{}", (j % 2) + 1);
                if let Some(value) = cache_clone.get(&key) {
                    println!("Reader {}: {} = {}", i, key, value);
                }
                thread::sleep(Duration::from_millis(10));
            }
        });
        handles.push(handle);
    }

    // Spawn writer thread
    let cache_clone = cache.clone();
    let writer_handle = thread::spawn(move || {
        for i in 3..6 {
            let key = format!("user:{}", i);
            let value = format!("User{}", i);
            cache_clone.insert(key.clone(), value.clone());
            println!("Writer: inserted {} = {}", key, value);
            thread::sleep(Duration::from_millis(50));
        }
    });

    // Wait for all threads
    for handle in handles {
        handle.join().unwrap();
    }
    writer_handle.join().unwrap();

    println!("Final cache size: {}", cache.len());
}
}

Advanced Patterns

1. Lazy Initialization with OnceCell

#![allow(unused)]
fn main() {
use std::cell::OnceCell;
use std::sync::OnceLock;

struct LazyData {
    expensive_computation: OnceCell<String>,
}

impl LazyData {
    fn new() -> Self {
        LazyData {
            expensive_computation: OnceCell::new(),
        }
    }

    fn get_data(&self) -> &str {
        self.expensive_computation.get_or_init(|| {
            println!("Performing expensive computation...");
            thread::sleep(Duration::from_millis(100));
            "Computed result".to_string()
        })
    }
}

// Thread-safe lazy initialization
static GLOBAL_DATA: OnceLock<String> = OnceLock::new();

fn get_global_data() -> &'static str {
    GLOBAL_DATA.get_or_init(|| {
        println!("Initializing global data...");
        "Global computed result".to_string()
    })
}
}

2. Cache with Interior Mutability

#![allow(unused)]
fn main() {
use std::cell::RefCell;
use std::collections::HashMap;

struct MemoizedFunction<F, K, V>
where
    F: Fn(&K) -> V,
    K: Clone + Eq + std::hash::Hash,
    V: Clone,
{
    function: F,
    cache: RefCell<HashMap<K, V>>,
}

impl<F, K, V> MemoizedFunction<F, K, V>
where
    F: Fn(&K) -> V,
    K: Clone + Eq + std::hash::Hash,
    V: Clone,
{
    fn new(function: F) -> Self {
        MemoizedFunction {
            function,
            cache: RefCell::new(HashMap::new()),
        }
    }

    fn call(&self, key: &K) -> V {
        // Try to get from cache first
        if let Some(cached) = self.cache.borrow().get(key) {
            return cached.clone();
        }

        // Compute and cache result
        let result = (self.function)(key);
        self.cache.borrow_mut().insert(key.clone(), result.clone());
        result
    }

    fn clear_cache(&self) {
        self.cache.borrow_mut().clear();
    }
}

fn demonstrate_memoization() {
    let expensive_fn = |n: &u32| -> u64 {
        println!("Computing fibonacci({})...", n);
        thread::sleep(Duration::from_millis(100));

        // Simple fibonacci (inefficient on purpose)
        match *n {
            0 => 0,
            1 => 1,
            n => {
                let mut a = 0;
                let mut b = 1;
                for _ in 2..=n {
                    let temp = a + b;
                    a = b;
                    b = temp;
                }
                b
            }
        }
    };

    let memoized = MemoizedFunction::new(expensive_fn);

    // First call - computes
    println!("First call: {}", memoized.call(&10));

    // Second call - cached
    println!("Second call: {}", memoized.call(&10));

    // Different input - computes
    println!("Different input: {}", memoized.call(&15));
}
}

Complete Example and Usage

fn main() {
    println!("=== Interior Mutability Pattern Examples ===\n");

    // 1. Cell example
    println!("1. Cell<T> for Copy types:");
    demonstrate_cell();

    // 2. RefCell example
    println!("\n2. RefCell<T> for runtime borrow checking:");
    demonstrate_refcell();

    // 3. Mutex example
    println!("\n3. Mutex<T> for thread-safe interior mutability:");
    demonstrate_mutex();

    // 4. RwLock example
    println!("\n4. RwLock<T> for reader-writer patterns:");
    demonstrate_rwlock();

    // 5. Lazy initialization
    println!("\n5. Lazy initialization with OnceCell:");
    let lazy = LazyData::new();
    println!("First access: {}", lazy.get_data());
    println!("Second access: {}", lazy.get_data());

    println!("Global data: {}", get_global_data());
    println!("Global data again: {}", get_global_data());

    // 6. Memoization example
    println!("\n6. Memoization with RefCell:");
    demonstrate_memoization();

    // 7. Error handling with interior mutability
    println!("\n7. Error handling:");
    demonstrate_error_handling();
}

fn demonstrate_error_handling() {
    use std::cell::RefCell;

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

    // This will panic - demonstrates runtime borrow checking
    // let _borrow1 = data.borrow_mut();
    // let _borrow2 = data.borrow_mut(); // Panic: already borrowed

    // Safe approach - check before borrowing
    match data.try_borrow_mut() {
        Ok(mut borrowed) => {
            borrowed.push(4);
            println!("Successfully modified: {:?}", *borrowed);
        }
        Err(e) => {
            println!("Failed to borrow: {}", e);
        }
    }

    // Demonstrate proper scoping
    {
        let mut borrowed = data.borrow_mut();
        borrowed.push(5);
    } // Borrow released here

    // Now we can borrow again
    println!("Final data: {:?}", data.borrow());
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cell_basic_operations() {
        let cell = Cell::new(42);
        assert_eq!(cell.get(), 42);

        cell.set(100);
        assert_eq!(cell.get(), 100);
    }

    #[test]
    fn test_refcell_borrowing() {
        let refcell = RefCell::new(vec![1, 2, 3]);

        // Immutable borrow
        {
            let borrowed = refcell.borrow();
            assert_eq!(borrowed.len(), 3);
        }

        // Mutable borrow
        {
            let mut borrowed = refcell.borrow_mut();
            borrowed.push(4);
        }

        assert_eq!(refcell.borrow().len(), 4);
    }

    #[test]
    #[should_panic(expected = "already borrowed")]
    fn test_refcell_panic_on_multiple_mut_borrow() {
        let refcell = RefCell::new(42);
        let _borrow1 = refcell.borrow_mut();
        let _borrow2 = refcell.borrow_mut(); // This should panic
    }

    #[test]
    fn test_try_borrow() {
        let refcell = RefCell::new(42);
        let _borrow1 = refcell.borrow_mut();

        // try_borrow should fail gracefully
        assert!(refcell.try_borrow().is_err());
        assert!(refcell.try_borrow_mut().is_err());
    }
}

Performance Characteristics

TypeRuntime CheckThread SafeUse Case
Cell<T>NoneNoCopy types, simple mutation
RefCell<T>Borrow checkingNoComplex types, single thread
Mutex<T>Lock contentionYesMulti-threaded mutation
RwLock<T>Reader/writer locksYesMany readers, few writers

Best Practices

  1. Prefer compile-time borrowing when possible
  2. Use Cell<T> for simple Copy types - it's the most efficient
  3. Use RefCell<T> sparingly - it moves errors from compile-time to runtime
  4. Handle RefCell panics gracefully with try_borrow methods
  5. Use Mutex<T> only when you need thread safety - it has overhead
  6. Consider RwLock<T> for read-heavy workloads
  7. Minimize the scope of borrows to avoid conflicts

When to Use Interior Mutability

Use interior mutability when:

  • You need shared ownership with mutation (Rc<RefCell<T>>)
  • Implementing caches or memoization
  • Working with observer patterns
  • Interfacing with C code that expects mutable data
  • The borrow checker is too conservative for your use case

Avoid interior mutability when:

  • Normal borrowing rules work fine
  • You can restructure your code to avoid the need
  • Performance is critical (runtime checks have cost)
  • You want compile-time guarantees

Core Rust concepts introduced

Building on previous patterns, the Interior Mutability pattern introduces several new concepts about runtime safety and shared mutation:

1. Interior Mutability vs Inherited Mutability

#![allow(unused)]
fn main() {
// Inherited mutability - from the binding
let mut x = 5;
x = 10;  // OK because binding is mutable

// Interior mutability - through the type
let x = Cell::new(5);  // binding is immutable
x.set(10);             // OK because Cell provides interior mutability
}
  • Inherited mutability: Mutability comes from the variable binding (let mut)
  • Interior mutability: Type allows mutation through immutable references
  • Orthogonal concepts: Interior mutability works regardless of binding mutability
  • Safety trade-off: Moves some checks from compile-time to runtime

2. Cell<T> for Copy Types

#![allow(unused)]
fn main() {
use std::cell::Cell;

let counter = Cell::new(0_u32);
counter.set(counter.get() + 1);  // Replace entire value
}
  • Copy types only: Cell<T> requires T: Copy
  • Value replacement: Can only get/set entire values, not references
  • Zero runtime cost: No additional overhead beyond the operation
  • Thread-local only: Not thread-safe, but very efficient

3. RefCell<T> for Runtime Borrow Checking

#![allow(unused)]
fn main() {
use std::cell::RefCell;

let data = RefCell::new(vec![1, 2, 3]);
let borrowed = data.borrow_mut();  // Runtime borrow check
}
  • Runtime borrow checking: Same rules as compile-time, but checked at runtime
  • Dynamic borrowing: borrow() and borrow_mut() return smart pointers
  • Panic on violation: Runtime panic if borrow rules are violated
  • try_borrow methods: Non-panicking variants that return Result

4. Mutex<T> for Thread-Safe Interior Mutability

#![allow(unused)]
fn main() {
use std::sync::Mutex;

let data = Mutex::new(42);
let mut guard = data.lock().unwrap();  // Acquire lock
*guard = 100;  // Mutate through guard
}
  • Thread-safe mutation: Can be shared across threads
  • Lock acquisition: lock() blocks until lock is available
  • Lock guards: RAII guards automatically release locks
  • Poisoning: Mutex becomes "poisoned" if a thread panics while holding the lock

5. Runtime vs Compile-Time Borrow Checking

#![allow(unused)]
fn main() {
// Compile-time checking
fn compile_time(data: &mut Vec<i32>) {
    data.push(1);  // ✅ Checked at compile time
}

// Runtime checking
fn runtime_checking(data: &RefCell<Vec<i32>>) {
    let mut borrowed = data.borrow_mut();  // ✅ Checked at runtime
    borrowed.push(1);
    // let another = data.borrow_mut();  // ❌ Would panic at runtime
}
}
  • Compile-time: Zero runtime cost, but less flexible
  • Runtime: More flexible, but potential for runtime panics
  • Error discovery: Compile-time errors vs runtime panics
  • Performance impact: Runtime checks have small overhead

6. Shared Ownership with Rc<RefCell<T>>

#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::cell::RefCell;

let shared_data = Rc::new(RefCell::new(vec![1, 2, 3]));
let clone1 = shared_data.clone();  // Reference counting
let clone2 = shared_data.clone();

// Multiple owners can all mutate the data
clone1.borrow_mut().push(4);
clone2.borrow_mut().push(5);
}
  • Reference counting: Rc<T> provides shared ownership
  • Interior mutability: RefCell<T> allows mutation through shared references
  • Common pattern: Rc<RefCell<T>> for shared mutable data in single-threaded contexts
  • Thread-safe alternative: Arc<Mutex<T>> for multi-threaded scenarios

7. Memory Safety Trade-offs

#![allow(unused)]
fn main() {
// Compile-time safety
let mut data = vec![1, 2, 3];
let reference = &data[0];
// data.push(4);  // ❌ Compile error: can't mutate while borrowed

// Runtime safety
let data = RefCell::new(vec![1, 2, 3]);
let reference = data.borrow();
let first = reference[0];
// let mut mutable = data.borrow_mut();  // ❌ Runtime panic: already borrowed
}
  • Compile-time guarantees: Impossible to violate borrowing rules
  • Runtime flexibility: Can implement patterns impossible with compile-time checks
  • Error timing: Compile errors vs runtime panics
  • Performance: Zero-cost vs small runtime overhead

These concepts show how Rust provides escape hatches from its strict compile-time borrowing rules while maintaining memory safety through runtime checks. Interior mutability is a powerful tool that should be used judiciously - prefer compile-time safety when possible, but don't hesitate to use interior mutability when the flexibility is genuinely needed.

Cow (Clone on Write) Pattern

Clone on Write (Cow) is a optimization pattern that defers cloning expensive data until mutation is actually needed. Rust's Cow<'a, T> type provides this functionality, allowing functions to accept both owned and borrowed data while only cloning when necessary.

Problem Statement

You want to write functions that can accept both owned and borrowed data, and you want to avoid unnecessary cloning. Sometimes you need to return data that might be a reference to input data (zero-copy) or newly allocated data (when modification is needed).

Understanding Clone on Write

The core idea is simple: keep a reference to data as long as possible, only clone when you need to modify it.

#![allow(unused)]
fn main() {
use std::borrow::Cow;

// Without Cow - always clones
fn process_string_clone(input: &str) -> String {
    let mut result = input.to_string();  // Always clones
    if input.contains("ERROR") {
        result = result.replace("ERROR", "WARNING");
    }
    result
}

// With Cow - clones only when needed
fn process_string_cow(input: &str) -> Cow<str> {
    if input.contains("ERROR") {
        Cow::Owned(input.replace("ERROR", "WARNING"))  // Clone only when modifying
    } else {
        Cow::Borrowed(input)  // No cloning needed
    }
}
}

Cow<'a, T> Enum Structure

#![allow(unused)]
fn main() {
pub enum Cow<'a, T: ?Sized + ToOwned> {
    Borrowed(&'a T),
    Owned(<T as ToOwned>::Owned),
}
}

The Cow enum has two variants:

  • Borrowed(&'a T) - holds a reference to existing data
  • Owned(T::Owned) - holds owned data

Basic Cow Usage

String Processing Examples

#![allow(unused)]
fn main() {
use std::borrow::Cow;

fn remove_whitespace(input: &str) -> Cow<str> {
    if input.contains(char::is_whitespace) {
        // Need to modify - create owned string
        Cow::Owned(input.chars().filter(|c| !c.is_whitespace()).collect())
    } else {
        // No modification needed - return borrowed
        Cow::Borrowed(input)
    }
}

fn add_prefix(input: &str, prefix: &str) -> Cow<str> {
    if input.starts_with(prefix) {
        Cow::Borrowed(input)  // Already has prefix
    } else {
        Cow::Owned(format!("{}{}", prefix, input))  // Need to add prefix
    }
}

fn normalize_case(input: &str) -> Cow<str> {
    if input.chars().all(|c| c.is_lowercase()) {
        Cow::Borrowed(input)  // Already lowercase
    } else {
        Cow::Owned(input.to_lowercase())  // Convert to lowercase
    }
}

fn demonstrate_string_processing() {
    println!("=== String Processing with Cow ===");

    let text1 = "hello world";
    let text2 = "hello   world   with   spaces";

    println!("Original: '{}'", text1);
    match remove_whitespace(text1) {
        Cow::Borrowed(s) => println!("No whitespace (borrowed): '{}'", s),
        Cow::Owned(s) => println!("Whitespace removed (owned): '{}'", s),
    }

    println!("Original: '{}'", text2);
    match remove_whitespace(text2) {
        Cow::Borrowed(s) => println!("No whitespace (borrowed): '{}'", s),
        Cow::Owned(s) => println!("Whitespace removed (owned): '{}'", s),
    }

    // Prefix examples
    let url1 = "https://example.com";
    let url2 = "example.com";

    println!("URL1: {} -> {}", url1, add_prefix(url1, "https://"));
    println!("URL2: {} -> {}", url2, add_prefix(url2, "https://"));
}
}

Path Processing

#![allow(unused)]
fn main() {
use std::borrow::Cow;
use std::path::Path;

fn normalize_path(path: &Path) -> Cow<Path> {
    // Check if path needs normalization
    let path_str = path.to_string_lossy();

    if path_str.contains("//") || path_str.contains("/./") || path_str.contains("/../") {
        // Need to normalize - create owned PathBuf
        let mut components = Vec::new();
        for component in path.components() {
            match component {
                std::path::Component::CurDir => {
                    // Skip current directory "."
                }
                std::path::Component::ParentDir => {
                    // Handle parent directory ".."
                    components.pop();
                }
                other => {
                    components.push(other);
                }
            }
        }

        let mut normalized = std::path::PathBuf::new();
        for component in components {
            normalized.push(component);
        }

        Cow::Owned(normalized)
    } else {
        // Path is already normalized
        Cow::Borrowed(path)
    }
}

fn ensure_extension(path: &Path, ext: &str) -> Cow<Path> {
    if path.extension().and_then(|e| e.to_str()) == Some(ext) {
        Cow::Borrowed(path)
    } else {
        let mut owned = path.to_path_buf();
        owned.set_extension(ext);
        Cow::Owned(owned)
    }
}

fn demonstrate_path_processing() {
    println!("\n=== Path Processing with Cow ===");

    let paths = [
        Path::new("/home/user/documents"),
        Path::new("/home/user//documents/./file.txt"),
        Path::new("/home/user/documents/../downloads/file.txt"),
    ];

    for path in &paths {
        println!("Original: {:?}", path);
        match normalize_path(path) {
            Cow::Borrowed(p) => println!("  Already normalized (borrowed): {:?}", p),
            Cow::Owned(p) => println!("  Normalized (owned): {:?}", p),
        }
    }

    // Extension examples
    let file1 = Path::new("document.txt");
    let file2 = Path::new("document");

    println!("File1: {:?} -> {:?}", file1, ensure_extension(file1, "txt"));
    println!("File2: {:?} -> {:?}", file2, ensure_extension(file2, "txt"));
}
}

Advanced Cow Patterns

Configuration Processing

#![allow(unused)]
fn main() {
use std::borrow::Cow;
use std::collections::HashMap;

#[derive(Debug, Clone)]
struct Config {
    settings: HashMap<String, String>,
}

impl Config {
    fn new() -> Self {
        Config {
            settings: HashMap::new(),
        }
    }

    fn get_or_default<'a>(&'a self, key: &str, default: &'a str) -> Cow<'a, str> {
        self.settings.get(key)
            .map(|s| Cow::Borrowed(s.as_str()))
            .unwrap_or(Cow::Borrowed(default))
    }

    fn get_processed<'a>(&'a self, key: &str) -> Option<Cow<'a, str>> {
        self.settings.get(key).map(|value| {
            if value.starts_with('$') {
                // Environment variable substitution
                let var_name = &value[1..];
                match std::env::var(var_name) {
                    Ok(env_value) => Cow::Owned(env_value),
                    Err(_) => Cow::Borrowed(value.as_str()),
                }
            } else {
                Cow::Borrowed(value.as_str())
            }
        })
    }
}

fn demonstrate_config() {
    println!("\n=== Configuration Processing ===");

    let mut config = Config::new();
    config.settings.insert("host".to_string(), "localhost".to_string());
    config.settings.insert("port".to_string(), "8080".to_string());
    config.settings.insert("env_path".to_string(), "$PATH".to_string());

    // Get with defaults
    println!("Host: {}", config.get_or_default("host", "0.0.0.0"));
    println!("Timeout: {}", config.get_or_default("timeout", "30"));

    // Process environment variables
    if let Some(path) = config.get_processed("env_path") {
        match path {
            Cow::Borrowed(s) => println!("Path (borrowed): {}", s),
            Cow::Owned(s) => println!("Path (from env): {}", &s[..50.min(s.len())]),
        }
    }
}
}

Data Transformation Pipeline

#![allow(unused)]
fn main() {
use std::borrow::Cow;

trait Transform {
    fn transform<'a>(&self, input: Cow<'a, str>) -> Cow<'a, str>;
}

struct TrimTransform;
impl Transform for TrimTransform {
    fn transform<'a>(&self, input: Cow<'a, str>) -> Cow<'a, str> {
        let trimmed = input.trim();
        if trimmed.len() == input.len() {
            input  // No change needed
        } else {
            Cow::Owned(trimmed.to_string())
        }
    }
}

struct UppercaseTransform;
impl Transform for UppercaseTransform {
    fn transform<'a>(&self, input: Cow<'a, str>) -> Cow<'a, str> {
        if input.chars().all(|c| c.is_uppercase() || !c.is_alphabetic()) {
            input  // Already uppercase
        } else {
            Cow::Owned(input.to_uppercase())
        }
    }
}

struct ReplaceTransform {
    from: String,
    to: String,
}

impl ReplaceTransform {
    fn new(from: &str, to: &str) -> Self {
        ReplaceTransform {
            from: from.to_string(),
            to: to.to_string(),
        }
    }
}

impl Transform for ReplaceTransform {
    fn transform<'a>(&self, input: Cow<'a, str>) -> Cow<'a, str> {
        if input.contains(&self.from) {
            Cow::Owned(input.replace(&self.from, &self.to))
        } else {
            input
        }
    }
}

struct Pipeline {
    transforms: Vec<Box<dyn Transform>>,
}

impl Pipeline {
    fn new() -> Self {
        Pipeline {
            transforms: Vec::new(),
        }
    }

    fn add_transform(mut self, transform: Box<dyn Transform>) -> Self {
        self.transforms.push(transform);
        self
    }

    fn process<'a>(&self, input: &'a str) -> Cow<'a, str> {
        let mut current = Cow::Borrowed(input);
        for transform in &self.transforms {
            current = transform.transform(current);
        }
        current
    }
}

fn demonstrate_pipeline() {
    println!("\n=== Transformation Pipeline ===");

    let pipeline = Pipeline::new()
        .add_transform(Box::new(TrimTransform))
        .add_transform(Box::new(ReplaceTransform::new("error", "warning")))
        .add_transform(Box::new(UppercaseTransform));

    let inputs = [
        "  hello world  ",
        "ALREADY UPPERCASE",
        "  this has an error message  ",
        "clean text",
    ];

    for input in &inputs {
        println!("Input: '{}'", input);
        match pipeline.process(input) {
            Cow::Borrowed(s) => println!("  Output (borrowed): '{}'", s),
            Cow::Owned(s) => println!("  Output (owned): '{}'", s),
        }
    }
}
}

Custom ToOwned Implementation

#![allow(unused)]
fn main() {
use std::borrow::{Cow, ToOwned};

// Custom type that can be borrowed/owned
#[derive(Debug, Clone, PartialEq)]
struct CustomData {
    values: Vec<i32>,
}

// Borrowed version
#[derive(Debug, PartialEq)]
struct CustomDataRef<'a> {
    values: &'a [i32],
}

impl ToOwned for CustomDataRef<'_> {
    type Owned = CustomData;

    fn to_owned(&self) -> Self::Owned {
        CustomData {
            values: self.values.to_vec(),
        }
    }
}

impl<'a> From<&'a CustomData> for CustomDataRef<'a> {
    fn from(data: &'a CustomData) -> Self {
        CustomDataRef {
            values: &data.values,
        }
    }
}

fn process_custom_data(input: CustomDataRef) -> Cow<CustomDataRef> {
    // Only clone if we need to modify
    if input.values.iter().any(|&x| x < 0) {
        // Need to filter out negative numbers
        let filtered: Vec<i32> = input.values.iter().filter(|&&x| x >= 0).copied().collect();
        let owned = CustomData { values: filtered };
        Cow::Owned(owned)
    } else {
        Cow::Borrowed(input)
    }
}

fn demonstrate_custom_toowned() {
    println!("\n=== Custom ToOwned Implementation ===");

    let data1 = CustomData { values: vec![1, 2, 3, 4, 5] };
    let data2 = CustomData { values: vec![1, -2, 3, -4, 5] };

    let ref1 = CustomDataRef::from(&data1);
    let ref2 = CustomDataRef::from(&data2);

    match process_custom_data(ref1) {
        Cow::Borrowed(r) => println!("No negatives (borrowed): {:?}", r),
        Cow::Owned(o) => println!("Filtered (owned): {:?}", o),
    }

    match process_custom_data(ref2) {
        Cow::Borrowed(r) => println!("No negatives (borrowed): {:?}", r),
        Cow::Owned(o) => println!("Filtered (owned): {:?}", o),
    }
}
}

Complete Example and Usage

fn main() {
    println!("=== Cow (Clone on Write) Pattern Examples ===");

    // Basic string processing
    demonstrate_string_processing();

    // Path processing
    demonstrate_path_processing();

    // Configuration processing
    demonstrate_config();

    // Transformation pipeline
    demonstrate_pipeline();

    // Custom ToOwned
    demonstrate_custom_toowned();

    // Performance comparison
    demonstrate_performance();
}

fn demonstrate_performance() {
    println!("\n=== Performance Comparison ===");

    let large_string = "a".repeat(1_000_000);
    let iterations = 1000;

    // Measure always-cloning approach
    let start = std::time::Instant::now();
    for _ in 0..iterations {
        let _result = always_clone(&large_string);
    }
    let always_clone_time = start.elapsed();

    // Measure cow approach (no modification needed)
    let start = std::time::Instant::now();
    for _ in 0..iterations {
        let _result = cow_approach(&large_string);
    }
    let cow_time = start.elapsed();

    println!("Always clone: {:?}", always_clone_time);
    println!("Cow (no modification): {:?}", cow_time);
    println!("Speedup: {:.2}x", always_clone_time.as_secs_f64() / cow_time.as_secs_f64());
}

fn always_clone(input: &str) -> String {
    let result = input.to_string();  // Always clone
    if result.contains("xyz") {
        result.replace("xyz", "abc")
    } else {
        result
    }
}

fn cow_approach(input: &str) -> Cow<str> {
    if input.contains("xyz") {
        Cow::Owned(input.replace("xyz", "abc"))
    } else {
        Cow::Borrowed(input)  // No clone needed
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cow_borrowed_case() {
        let input = "hello world";
        let result = remove_whitespace(input);

        match result {
            Cow::Borrowed(s) => assert_eq!(s, "hello world"),
            Cow::Owned(_) => panic!("Expected borrowed, got owned"),
        }
    }

    #[test]
    fn test_cow_owned_case() {
        let input = "hello   world";
        let result = remove_whitespace(input);

        match result {
            Cow::Borrowed(_) => panic!("Expected owned, got borrowed"),
            Cow::Owned(s) => assert_eq!(s, "helloworld"),
        }
    }

    #[test]
    fn test_prefix_addition() {
        assert_eq!(add_prefix("https://example.com", "https://"), "https://example.com");
        assert_eq!(add_prefix("example.com", "https://"), "https://example.com");
    }

    #[test]
    fn test_transformation_pipeline() {
        let pipeline = Pipeline::new()
            .add_transform(Box::new(TrimTransform))
            .add_transform(Box::new(UppercaseTransform));

        let result = pipeline.process("  hello  ");
        assert_eq!(result, "HELLO");
    }
}

When to Use Cow

Use Cow when:

  • You frequently don't need to modify input data
  • You want to avoid unnecessary allocations
  • You're building APIs that should accept both owned and borrowed data
  • You're implementing transformation pipelines
  • You need to conditionally modify data

Don't use Cow when:

  • You always need to modify the data
  • The data is small and cloning is cheap
  • The complexity isn't worth the optimization
  • You're working with non-cloneable data

Performance Characteristics

  • Best case: Zero allocations when no modification is needed
  • Worst case: Same cost as always cloning
  • Memory: Only uses extra memory when cloning is necessary
  • CPU: Small overhead for the enum check, big savings when avoiding clones

API Design with Cow

#![allow(unused)]
fn main() {
// Good: Flexible API that accepts both owned and borrowed
fn process_text(input: impl Into<Cow<str>>) -> Cow<str> {
    let text = input.into();
    // Process the text...
    text
}

// Usage:
process_text("borrowed string");           // &str
process_text(String::from("owned"));       // String
process_text(Cow::Borrowed("explicit"));   // Explicit Cow
}

Core Rust concepts introduced

Building on previous patterns, the Cow pattern introduces several new concepts related to memory efficiency and flexible APIs:

1. Cow<'a, T> Enum and Borrowed/Owned Variants

#![allow(unused)]
fn main() {
pub enum Cow<'a, T: ?Sized + ToOwned> {
    Borrowed(&'a T),                    // Reference to existing data
    Owned(<T as ToOwned>::Owned),       // Owned copy of the data
}
}
  • Delayed cloning: Only clone when mutation is actually needed
  • Lifetime parameter: 'a tracks how long borrowed data is valid
  • Type flexibility: Can hold either borrowed or owned data of the same logical type
  • Pattern matching: Use match to handle both cases explicitly

2. Lazy Cloning and Performance Optimization

#![allow(unused)]
fn main() {
fn process_data(input: &str) -> Cow<str> {
    if needs_modification(input) {
        Cow::Owned(expensive_transformation(input))  // Clone only when needed
    } else {
        Cow::Borrowed(input)  // Zero-cost when no modification needed
    }
}
}
  • Conditional cloning: Only allocate memory when modification is required
  • Best-case zero-cost: No allocation when data doesn't need changes
  • Worst-case equivalent: Same cost as always cloning when modification is needed
  • Memory efficiency: Significant savings in read-heavy workloads

3. Lifetime Elision in Return Types

#![allow(unused)]
fn main() {
// Explicit lifetimes
fn process<'a>(input: &'a str) -> Cow<'a, str> { ... }

// Lifetime elision (compiler infers)
fn process(input: &str) -> Cow<str> { ... }
}
  • Lifetime elision rules: Compiler can infer lifetimes in many cases
  • Input/output relationship: Lifetime of output tied to lifetime of input
  • API simplification: Less verbose function signatures
  • Borrow checker integration: Ensures borrowed data lives long enough

4. ToOwned Trait and Custom Implementations

#![allow(unused)]
fn main() {
pub trait ToOwned {
    type Owned: Borrow<Self>;

    fn to_owned(&self) -> Self::Owned;

    fn clone_into(&self, target: &mut Self::Owned) { ... }
}
}
  • Generalized cloning: More flexible than Clone for borrowed/owned relationships
  • Associated type: Owned represents the owned version of borrowed data
  • Standard implementations: str -> String, [T] -> Vec<T>, Path -> PathBuf
  • Custom implementations: Define your own borrowed/owned type pairs

5. Zero-Copy String Processing

#![allow(unused)]
fn main() {
fn remove_prefix(input: &str, prefix: &str) -> Cow<str> {
    if let Some(stripped) = input.strip_prefix(prefix) {
        Cow::Borrowed(stripped)  // Zero-copy slice of original string
    } else {
        Cow::Borrowed(input)     // No modification needed
    }
}
}
  • String slicing: Create views into existing strings without allocation
  • Conditional processing: Only allocate when modification is actually needed
  • Memory savings: Dramatic reduction in allocations for read-heavy operations
  • Cache-friendly: Better memory locality when avoiding allocations

6. API Design for Flexible Input Types

#![allow(unused)]
fn main() {
// Accept multiple input types
fn flexible_api(input: impl Into<Cow<str>>) -> String {
    let cow = input.into();
    cow.into_owned()  // Convert to owned String
}

// Usage examples:
flexible_api("string literal");        // &str -> Cow::Borrowed
flexible_api(String::from("owned"));   // String -> Cow::Owned
flexible_api(Cow::Borrowed("cow"));    // Direct Cow
}
  • Into<Cow<T>> pattern: Accept both borrowed and owned data seamlessly
  • Conversion traits: Automatic conversion from &T and T::Owned to Cow<T>
  • API flexibility: Single function signature handles multiple input types
  • User convenience: Callers don't need to think about ownership

7. Memory Efficiency Patterns

#![allow(unused)]
fn main() {
// Efficient transformation pipeline
fn transform_pipeline(input: &str) -> Cow<str> {
    let mut current = Cow::Borrowed(input);

    for transform in &transforms {
        current = transform.apply(current);  // Chain without unnecessary cloning
    }

    current
}
}
  • Pipeline efficiency: Avoid intermediate clones in transformation chains
  • Lazy evaluation: Defer expensive operations until absolutely necessary
  • Memory pressure reduction: Significant savings in allocation-heavy code
  • Performance monitoring: Easy to track when allocations actually occur

These concepts demonstrate how Rust enables both memory efficiency and API flexibility through the type system. Cow is a powerful pattern for building performant code that doesn't sacrifice usability, allowing you to optimize for the common case (no modification needed) while still handling the general case (modification required) correctly.

Command Pattern

The Command pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue operations, log requests, and support undo operations. In Rust, this pattern showcases the power of closures, function pointers, and different ways to capture and store computation.

Problem Statement

You need to decouple the object that invokes an operation from the object that performs it. You might want to queue operations, log them, support undo/redo, or pass operations as parameters. Traditional OOP uses command objects, but Rust's closures provide more flexible and efficient alternatives.

Traditional Object-Oriented Implementation

In Java, the Command pattern typically uses interfaces and classes:

// Command interface
interface Command {
    void execute();
    void undo();
}

// Receiver - the object that performs the actual work
class TextEditor {
    private StringBuilder content = new StringBuilder();

    public void insertText(String text) {
        content.append(text);
    }

    public void deleteText(int length) {
        int start = Math.max(0, content.length() - length);
        content.delete(start, content.length());
    }

    public String getContent() {
        return content.toString();
    }
}

// Concrete commands
class InsertCommand implements Command {
    private TextEditor editor;
    private String text;

    public InsertCommand(TextEditor editor, String text) {
        this.editor = editor;
        this.text = text;
    }

    @Override
    public void execute() {
        editor.insertText(text);
    }

    @Override
    public void undo() {
        editor.deleteText(text.length());
    }
}

// Invoker
class Macro {
    private List<Command> commands = new ArrayList<>();

    public void addCommand(Command command) {
        commands.add(command);
    }

    public void execute() {
        for (Command command : commands) {
            command.execute();
        }
    }
}

Rust Implementation

Rust provides multiple ways to implement the Command pattern, each with different trade-offs:

1. Function Pointers

Simple stateless commands using function pointers:

#![allow(unused)]
fn main() {
// Simple command using function pointers
type SimpleCommand = fn();

struct Calculator {
    value: f64,
}

impl Calculator {
    fn new() -> Self {
        Calculator { value: 0.0 }
    }

    fn add(&mut self, n: f64) {
        self.value += n;
        println!("Added {}, result: {}", n, self.value);
    }

    fn multiply(&mut self, n: f64) {
        self.value *= n;
        println!("Multiplied by {}, result: {}", n, self.value);
    }

    fn clear(&mut self) {
        self.value = 0.0;
        println!("Cleared, result: {}", self.value);
    }
}

// Function that creates commands
fn create_calculator_commands() -> Vec<fn(&mut Calculator)> {
    vec![
        |calc| calc.add(10.0),
        |calc| calc.multiply(2.0),
        |calc| calc.add(5.0),
        |calc| calc.clear(),
    ]
}

fn demonstrate_function_pointers() {
    println!("=== Function Pointer Commands ===");

    let mut calculator = Calculator::new();
    let commands = create_calculator_commands();

    for command in commands {
        command(&mut calculator);
    }
}
}

2. Closures for Stateful Commands

Closures can capture state, making them more flexible than function pointers:

#![allow(unused)]
fn main() {
use std::collections::VecDeque;

#[derive(Debug, Clone)]
struct TextEditor {
    content: String,
}

impl TextEditor {
    fn new() -> Self {
        TextEditor {
            content: String::new(),
        }
    }

    fn insert(&mut self, text: &str) {
        self.content.push_str(text);
    }

    fn delete(&mut self, count: usize) {
        let new_len = self.content.len().saturating_sub(count);
        self.content.truncate(new_len);
    }

    fn replace(&mut self, old: &str, new: &str) {
        self.content = self.content.replace(old, new);
    }

    fn get_content(&self) -> &str {
        &self.content
    }
}

// Command trait for undo/redo support
trait Command {
    fn execute(&self, editor: &mut TextEditor);
    fn undo(&self, editor: &mut TextEditor);
    fn description(&self) -> &str;
}

// Concrete command implementations
struct InsertCommand {
    text: String,
}

impl InsertCommand {
    fn new(text: String) -> Self {
        InsertCommand { text }
    }
}

impl Command for InsertCommand {
    fn execute(&self, editor: &mut TextEditor) {
        editor.insert(&self.text);
    }

    fn undo(&self, editor: &mut TextEditor) {
        editor.delete(self.text.len());
    }

    fn description(&self) -> &str {
        "Insert text"
    }
}

struct DeleteCommand {
    count: usize,
    deleted_text: std::cell::RefCell<Option<String>>,
}

impl DeleteCommand {
    fn new(count: usize) -> Self {
        DeleteCommand {
            count,
            deleted_text: std::cell::RefCell::new(None),
        }
    }
}

impl Command for DeleteCommand {
    fn execute(&self, editor: &mut TextEditor) {
        let content = editor.get_content();
        let start = content.len().saturating_sub(self.count);
        let deleted = content[start..].to_string();
        *self.deleted_text.borrow_mut() = Some(deleted);
        editor.delete(self.count);
    }

    fn undo(&self, editor: &mut TextEditor) {
        if let Some(ref text) = *self.deleted_text.borrow() {
            editor.insert(text);
        }
    }

    fn description(&self) -> &str {
        "Delete text"
    }
}

// Command manager with undo/redo support
struct CommandManager {
    history: VecDeque<Box<dyn Command>>,
    current_position: usize,
    max_history: usize,
}

impl CommandManager {
    fn new(max_history: usize) -> Self {
        CommandManager {
            history: VecDeque::new(),
            current_position: 0,
            max_history,
        }
    }

    fn execute(&mut self, command: Box<dyn Command>, editor: &mut TextEditor) {
        // Execute the command
        command.execute(editor);

        // Remove any commands after current position (for redo)
        self.history.truncate(self.current_position);

        // Add new command
        self.history.push_back(command);
        self.current_position = self.history.len();

        // Maintain max history size
        while self.history.len() > self.max_history {
            self.history.pop_front();
            self.current_position = self.current_position.saturating_sub(1);
        }
    }

    fn undo(&mut self, editor: &mut TextEditor) -> bool {
        if self.current_position > 0 {
            self.current_position -= 1;
            if let Some(command) = self.history.get(self.current_position) {
                command.undo(editor);
                return true;
            }
        }
        false
    }

    fn redo(&mut self, editor: &mut TextEditor) -> bool {
        if self.current_position < self.history.len() {
            if let Some(command) = self.history.get(self.current_position) {
                command.execute(editor);
                self.current_position += 1;
                return true;
            }
        }
        false
    }

    fn get_history(&self) -> Vec<String> {
        self.history.iter()
            .enumerate()
            .map(|(i, cmd)| {
                let marker = if i < self.current_position { "✓" } else { "-" };
                format!("{} {}", marker, cmd.description())
            })
            .collect()
    }
}

fn demonstrate_undo_redo() {
    println!("\n=== Undo/Redo Commands ===");

    let mut editor = TextEditor::new();
    let mut manager = CommandManager::new(10);

    // Execute some commands
    manager.execute(Box::new(InsertCommand::new("Hello ".to_string())), &mut editor);
    println!("After insert: '{}'", editor.get_content());

    manager.execute(Box::new(InsertCommand::new("World".to_string())), &mut editor);
    println!("After insert: '{}'", editor.get_content());

    manager.execute(Box::new(DeleteCommand::new(5)), &mut editor);
    println!("After delete: '{}'", editor.get_content());

    // Undo operations
    println!("\nUndoing...");
    manager.undo(&mut editor);
    println!("After undo: '{}'", editor.get_content());

    manager.undo(&mut editor);
    println!("After undo: '{}'", editor.get_content());

    // Redo operations
    println!("\nRedoing...");
    manager.redo(&mut editor);
    println!("After redo: '{}'", editor.get_content());

    println!("\nCommand history:");
    for (i, desc) in manager.get_history().iter().enumerate() {
        println!("  {}: {}", i + 1, desc);
    }
}
}

3. Boxed Closures for Dynamic Commands

For more flexibility, store closures in boxes:

#![allow(unused)]
fn main() {
type BoxedCommand<T> = Box<dyn Fn(&mut T) + Send + Sync>;

struct EventSystem<T> {
    commands: Vec<BoxedCommand<T>>,
}

impl<T> EventSystem<T> {
    fn new() -> Self {
        EventSystem {
            commands: Vec::new(),
        }
    }

    fn add_command<F>(&mut self, command: F)
    where
        F: Fn(&mut T) + Send + Sync + 'static,
    {
        self.commands.push(Box::new(command));
    }

    fn execute_all(&self, target: &mut T) {
        for command in &self.commands {
            command(target);
        }
    }

    fn clear(&mut self) {
        self.commands.clear();
    }
}

#[derive(Debug)]
struct GameState {
    score: u32,
    level: u32,
    player_name: String,
}

impl GameState {
    fn new(player_name: String) -> Self {
        GameState {
            score: 0,
            level: 1,
            player_name,
        }
    }

    fn add_score(&mut self, points: u32) {
        self.score += points;
    }

    fn level_up(&mut self) {
        self.level += 1;
        println!("Level up! Now at level {}", self.level);
    }

    fn reset(&mut self) {
        self.score = 0;
        self.level = 1;
    }
}

fn demonstrate_event_system() {
    println!("\n=== Event System with Boxed Closures ===");

    let mut game = GameState::new("Alice".to_string());
    let mut events = EventSystem::new();

    // Add various event handlers
    events.add_command(|state: &mut GameState| {
        state.add_score(100);
        println!("Score bonus applied! Score: {}", state.score);
    });

    events.add_command(|state: &mut GameState| {
        if state.score >= 500 {
            state.level_up();
        }
    });

    let bonus_multiplier = 2;
    events.add_command(move |state: &mut GameState| {
        let bonus = state.level * bonus_multiplier * 10;
        state.add_score(bonus);
        println!("Level bonus: {} points", bonus);
    });

    println!("Initial state: {:?}", game);

    // Execute events multiple times
    for round in 1..=6 {
        println!("\nRound {}:", round);
        events.execute_all(&mut game);
        println!("State: {:?}", game);
    }
}
}

4. Advanced: Async Commands

Commands that perform asynchronous operations:

#![allow(unused)]
fn main() {
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;

type AsyncCommand<T> = Box<dyn Fn(&mut T) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;

struct AsyncEventSystem<T> {
    commands: Vec<AsyncCommand<T>>,
}

impl<T> AsyncEventSystem<T>
where
    T: Send + 'static,
{
    fn new() -> Self {
        AsyncEventSystem {
            commands: Vec::new(),
        }
    }

    fn add_command<F, Fut>(&mut self, command: F)
    where
        F: Fn(&mut T) -> Fut + Send + Sync + 'static,
        Fut: Future<Output = ()> + Send + 'static,
    {
        self.commands.push(Box::new(move |target| {
            Box::pin(command(target))
        }));
    }

    async fn execute_all(&self, target: &mut T) {
        for command in &self.commands {
            command(target).await;
        }
    }
}

#[derive(Debug)]
struct NetworkService {
    connected: bool,
    message_count: u32,
}

impl NetworkService {
    fn new() -> Self {
        NetworkService {
            connected: false,
            message_count: 0,
        }
    }

    async fn connect(&mut self) {
        println!("Connecting to network...");
        tokio::time::sleep(Duration::from_millis(100)).await;
        self.connected = true;
        println!("Connected!");
    }

    async fn send_message(&mut self, message: &str) {
        if self.connected {
            println!("Sending: {}", message);
            tokio::time::sleep(Duration::from_millis(50)).await;
            self.message_count += 1;
            println!("Message sent! Total: {}", self.message_count);
        } else {
            println!("Cannot send message: not connected");
        }
    }

    async fn disconnect(&mut self) {
        if self.connected {
            println!("Disconnecting...");
            tokio::time::sleep(Duration::from_millis(50)).await;
            self.connected = false;
            println!("Disconnected!");
        }
    }
}

async fn demonstrate_async_commands() {
    println!("\n=== Async Commands ===");

    let mut service = NetworkService::new();
    let mut async_events = AsyncEventSystem::new();

    // Add async commands
    async_events.add_command(|service: &mut NetworkService| async move {
        service.connect().await;
    });

    async_events.add_command(|service: &mut NetworkService| async move {
        service.send_message("Hello, World!").await;
    });

    async_events.add_command(|service: &mut NetworkService| async move {
        service.send_message("How are you?").await;
    });

    async_events.add_command(|service: &mut NetworkService| async move {
        service.disconnect().await;
    });

    println!("Initial state: {:?}", service);
    async_events.execute_all(&mut service).await;
    println!("Final state: {:?}", service);
}
}

Complete Example and Usage

// Note: This would need to be in an async context for the async example
fn main() {
    println!("=== Command Pattern Examples ===");

    // 1. Function pointer commands
    demonstrate_function_pointers();

    // 2. Undo/redo with trait objects
    demonstrate_undo_redo();

    // 3. Event system with boxed closures
    demonstrate_event_system();

    // 4. Macro system demonstration
    demonstrate_macro_system();

    // Note: async example would be run with:
    // tokio::runtime::Runtime::new().unwrap().block_on(demonstrate_async_commands());
}

fn demonstrate_macro_system() {
    println!("\n=== Macro System ===");

    #[derive(Debug, Default)]
    struct Document {
        content: String,
        saved: bool,
    }

    impl Document {
        fn write(&mut self, text: &str) {
            self.content.push_str(text);
            self.saved = false;
        }

        fn save(&mut self) {
            self.saved = true;
            println!("Document saved!");
        }

        fn clear(&mut self) {
            self.content.clear();
            self.saved = false;
        }
    }

    // Macro that records and can replay commands
    struct Macro {
        commands: Vec<Box<dyn Fn(&mut Document)>>,
        name: String,
    }

    impl Macro {
        fn new(name: String) -> Self {
            Macro {
                commands: Vec::new(),
                name,
            }
        }

        fn record<F>(&mut self, command: F)
        where
            F: Fn(&mut Document) + 'static,
        {
            self.commands.push(Box::new(command));
        }

        fn execute(&self, document: &mut Document) {
            println!("Executing macro '{}':", self.name);
            for (i, command) in self.commands.iter().enumerate() {
                println!("  Step {}: Executing command", i + 1);
                command(document);
            }
        }

        fn command_count(&self) -> usize {
            self.commands.len()
        }
    }

    let mut doc = Document::default();
    let mut greeting_macro = Macro::new("Greeting".to_string());

    // Record commands in macro
    greeting_macro.record(|d| d.write("Hello, "));
    greeting_macro.record(|d| d.write("World!"));
    greeting_macro.record(|d| d.write("\nWelcome to Rust!"));
    greeting_macro.record(|d| d.save());

    println!("Document before macro: {:?}", doc);
    println!("Macro has {} commands", greeting_macro.command_count());

    greeting_macro.execute(&mut doc);
    println!("Document after macro: {:?}", doc);

    // Execute macro again
    println!("\nExecuting macro again:");
    greeting_macro.execute(&mut doc);
    println!("Document after second execution: {:?}", doc);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_command_manager_undo_redo() {
        let mut editor = TextEditor::new();
        let mut manager = CommandManager::new(5);

        // Execute commands
        manager.execute(Box::new(InsertCommand::new("Hello".to_string())), &mut editor);
        manager.execute(Box::new(InsertCommand::new(" World".to_string())), &mut editor);

        assert_eq!(editor.get_content(), "Hello World");

        // Undo
        assert!(manager.undo(&mut editor));
        assert_eq!(editor.get_content(), "Hello");

        assert!(manager.undo(&mut editor));
        assert_eq!(editor.get_content(), "");

        // Can't undo further
        assert!(!manager.undo(&mut editor));

        // Redo
        assert!(manager.redo(&mut editor));
        assert_eq!(editor.get_content(), "Hello");

        assert!(manager.redo(&mut editor));
        assert_eq!(editor.get_content(), "Hello World");

        // Can't redo further
        assert!(!manager.redo(&mut editor));
    }

    #[test]
    fn test_event_system() {
        let mut game = GameState::new("Test".to_string());
        let mut events = EventSystem::new();

        events.add_command(|state| state.add_score(100));
        events.add_command(|state| state.level_up());

        assert_eq!(game.score, 0);
        assert_eq!(game.level, 1);

        events.execute_all(&mut game);

        assert_eq!(game.score, 100);
        assert_eq!(game.level, 2);
    }

    #[test]
    fn test_macro_system() {
        let mut doc = Document::default();
        let mut macro_cmd = Macro::new("Test".to_string());

        macro_cmd.record(|d| d.write("Test"));
        macro_cmd.record(|d| d.save());

        assert_eq!(doc.content, "");
        assert!(!doc.saved);

        macro_cmd.execute(&mut doc);

        assert_eq!(doc.content, "Test");
        assert!(doc.saved);
    }
}

Performance Characteristics

ImplementationMemoryCall OverheadFlexibility
Function PointersMinimalNoneLow
ClosuresVariableMinimalHigh
Trait ObjectsBox allocationVirtual dispatchHigh
Async CommandsFuture + BoxAsync overheadVery High

Best Practices

  1. Use function pointers for simple, stateless commands
  2. Use closures when you need to capture state
  3. Use trait objects when you need undo/redo or complex command interfaces
  4. Consider async commands for I/O-bound operations
  5. Prefer move semantics to avoid lifetime issues in stored closures
  6. Use Send + Sync bounds for thread-safe command systems

When to Use Each Approach

  • Function pointers: Simple, stateless operations with maximum performance
  • Closures: When you need to capture environment variables
  • Trait objects: When you need complex command interfaces with undo/redo
  • Boxed closures: When you need runtime flexibility and don't mind allocation
  • Async commands: For I/O-bound or long-running operations

Core Rust concepts introduced

Building on all previous patterns, the Command pattern introduces the final set of advanced concepts related to function storage and execution:

1. Closure Traits: Fn, FnMut, and FnOnce

#![allow(unused)]
fn main() {
// FnOnce - can be called once, consumes captured variables
let consume_closure: Box<dyn FnOnce()> = Box::new(move || {
    drop(owned_data);  // Consumes owned_data
});

// FnMut - can be called multiple times, mutates captured variables
let mut counter = 0;
let mut mutate_closure: Box<dyn FnMut()> = Box::new(|| {
    counter += 1;  // Mutates captured variable
});

// Fn - can be called multiple times, only borrows captured variables
let data = vec![1, 2, 3];
let borrow_closure: Box<dyn Fn() -> usize> = Box::new(|| {
    data.len()  // Only borrows data
});
}
  • Closure hierarchy: Fn implements FnMut, FnMut implements FnOnce
  • Capture semantics: Determines how closures interact with captured variables
  • Call frequency: FnOnce (once), FnMut (multiple, exclusive), Fn (multiple, shared)
  • Automatic inference: Rust chooses the most restrictive trait that works

2. Function Pointers (fn) vs Closures

#![allow(unused)]
fn main() {
// Function pointer - no captured environment
fn regular_function(x: i32) -> i32 { x + 1 }
let fn_ptr: fn(i32) -> i32 = regular_function;

// Closure - can capture environment
let captured_value = 10;
let closure = |x: i32| x + captured_value;  // Captures captured_value

// Function pointers are a subset of Fn
fn takes_fn<F: Fn(i32) -> i32>(f: F) -> i32 { f(5) }
takes_fn(fn_ptr);    // ✅ Function pointer implements Fn
takes_fn(closure);   // ✅ Closure also implements Fn
}
  • Function pointers: Zero-sized, no captured environment, can be cast to/from raw pointers
  • Closures: May capture environment, implemented as anonymous structs
  • Compatibility: Function pointers implement the Fn trait
  • Performance: Function pointers have no indirection, closures may have captured data

3. Capture Modes: By Reference, By Value, By Move

#![allow(unused)]
fn main() {
let data = vec![1, 2, 3];
let immutable_ref = 42;
let mut mutable_data = String::from("hello");

// Capture by reference (default for variables that can be borrowed)
let by_ref = || {
    println!("Data length: {}", data.len());      // Borrows data
    println!("Immutable: {}", immutable_ref);     // Borrows immutable_ref
};

// Capture by mutable reference
let by_mut_ref = || {
    mutable_data.push_str(" world");  // Mutably borrows mutable_data
};

// Capture by value/move (explicit with 'move' keyword)
let by_move = move || {
    println!("Owned data: {:?}", data);           // Takes ownership of data
    println!("Owned ref: {}", immutable_ref);     // Copies immutable_ref
    println!("Owned string: {}", mutable_data);   // Takes ownership of mutable_data
};
}
  • Automatic inference: Rust chooses the minimal capture mode needed
  • move keyword: Forces capturing by value/move
  • Copy vs Move: Copy types are copied, non-Copy types are moved
  • Lifetime management: move closures don't depend on captured variable lifetimes

4. move Keyword for Closure Ownership

#![allow(unused)]
fn main() {
fn create_command(message: String) -> Box<dyn Fn() + Send + 'static> {
    // Without 'move', this would try to borrow 'message'
    // But 'message' won't live long enough for the returned closure
    Box::new(move || {
        println!("Command: {}", message);  // 'message' is moved into closure
    })
}

// Usage:
let cmd = create_command("Hello".to_string());
// 'message' is no longer accessible here, but closure owns it
cmd();  // Works fine
}
  • Ownership transfer: move transfers ownership of captured variables to closure
  • Static lifetimes: move closures can have 'static lifetime
  • Thread safety: Often required for closures sent across threads
  • Independence: move closures don't depend on original variable lifetimes

5. Boxed Closures for Dynamic Storage

#![allow(unused)]
fn main() {
// Store different types of closures in the same collection
let mut commands: Vec<Box<dyn Fn(&mut GameState)>> = Vec::new();

commands.push(Box::new(|state| state.add_score(100)));
commands.push(Box::new(move |state| {
    let bonus = captured_bonus;
    state.add_score(bonus);
}));

// Execute all commands
for command in &commands {
    command(&mut game_state);
}
}
  • Type erasure: Box<dyn Fn(...)> erases the specific closure type
  • Dynamic dispatch: Runtime method resolution through vtable
  • Collection storage: Store different closures in the same collection
  • Heap allocation: Closures are stored on the heap

6. Higher-Order Functions and Combinators

#![allow(unused)]
fn main() {
// Function that returns a function
fn create_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
    move |x| x * factor
}

// Function that takes a function as parameter
fn apply_operation<F>(operation: F, value: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    operation(value)
}

// Combinator pattern
fn combine_operations<F, G>(f: F, g: G) -> impl Fn(i32) -> i32
where
    F: Fn(i32) -> i32,
    G: Fn(i32) -> i32,
{
    move |x| g(f(x))  // Function composition
}
}
  • Higher-order functions: Functions that operate on other functions
  • Factory functions: Create specialized functions based on parameters
  • Function composition: Combine multiple functions into new functions
  • impl Fn return type: Return opaque closure types without boxing

7. Callback Patterns and Event Systems

#![allow(unused)]
fn main() {
struct EventEmitter<T> {
    listeners: Vec<Box<dyn Fn(&T) + Send + Sync>>,
}

impl<T> EventEmitter<T> {
    fn on<F>(&mut self, callback: F)
    where
        F: Fn(&T) + Send + Sync + 'static,
    {
        self.listeners.push(Box::new(callback));
    }

    fn emit(&self, event: &T) {
        for listener in &self.listeners {
            listener(event);
        }
    }
}
}
  • Observer pattern: Register callbacks for events
  • Thread safety: Send + Sync bounds for multi-threaded use
  • Event propagation: Notify multiple listeners of state changes
  • Decoupled communication: Loosely couple event producers and consumers

These concepts demonstrate Rust's powerful function system, from zero-cost function pointers to flexible closures with captured environments. The Command pattern showcases how Rust's ownership system and closure traits enable both performance and flexibility in storing and executing computation.