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

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.