Command Pattern

Encapsulate requests as objects with undo/redo support

advanced
commandbehavioralundo-redotransactions
🎮 Interactive Playground

What is the Command Pattern?

The Command pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations.

Key concepts:
  • Command: Encapsulates an action and its parameters
  • Invoker: Asks the command to execute
  • Receiver: Knows how to perform the operation
  • Client: Creates and configures commands
The Rust perspective:
  • Commands are naturally represented as closures or trait objects
  • Ownership makes undo/redo clean - commands own their state
  • Enums can represent command variants type-safely
  • FnOnce, FnMut, Fn traits provide built-in command abstractions
// Simple command trait
pub trait Command {
    fn execute(&mut self);
    fn undo(&mut self);
}

// Or using closures for simple cases
type SimpleCommand = Box<dyn FnMut()>;

When to Use Command Pattern

Appropriate use cases:
  1. Implementing undo/redo functionality
  2. Transaction systems with rollback
  3. Task queues and job schedulers
  4. Macro recording and playback
  5. Request logging and auditing
  6. Decoupling invoker from executor
When to avoid:
  1. Simple direct method calls suffice
  2. No need for queuing, logging, or undo
  3. Commands have complex interdependencies

Real-World Example 1: Text Editor with Undo/Redo (Desktop Application)

A full-featured text editor command system supporting undo, redo, and macro recording.

use std::collections::VecDeque;

/// The document being edited (Receiver)
#[derive(Debug, Clone)]
pub struct Document {
    content: String,
    cursor: usize,
    selection: Option<(usize, usize)>,
}

impl Document {
    pub fn new() -> Self {
        Self {
            content: String::new(),
            cursor: 0,
            selection: None,
        }
    }

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

    pub fn cursor(&self) -> usize {
        self.cursor
    }

    pub fn insert_at(&mut self, pos: usize, text: &str) {
        self.content.insert_str(pos, text);
    }

    pub fn delete_range(&mut self, start: usize, end: usize) -> String {
        let removed: String = self.content[start..end].to_string();
        self.content.replace_range(start..end, "");
        removed
    }

    pub fn set_cursor(&mut self, pos: usize) {
        self.cursor = pos.min(self.content.len());
    }

    pub fn select(&mut self, start: usize, end: usize) {
        self.selection = Some((start.min(end), start.max(end)));
    }

    pub fn clear_selection(&mut self) {
        self.selection = None;
    }
}

impl Default for Document {
    fn default() -> Self {
        Self::new()
    }
}

/// Command trait with undo support
pub trait Command: std::fmt::Debug {
    fn execute(&mut self, doc: &mut Document);
    fn undo(&mut self, doc: &mut Document);
    fn description(&self) -> &str;
}

/// Insert text command
#[derive(Debug)]
pub struct InsertCommand {
    position: usize,
    text: String,
}

impl InsertCommand {
    pub fn new(position: usize, text: impl Into<String>) -> Self {
        Self {
            position,
            text: text.into(),
        }
    }
}

impl Command for InsertCommand {
    fn execute(&mut self, doc: &mut Document) {
        doc.insert_at(self.position, &self.text);
        doc.set_cursor(self.position + self.text.len());
    }

    fn undo(&mut self, doc: &mut Document) {
        doc.delete_range(self.position, self.position + self.text.len());
        doc.set_cursor(self.position);
    }

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

/// Delete text command
#[derive(Debug)]
pub struct DeleteCommand {
    start: usize,
    end: usize,
    deleted_text: Option<String>,
}

impl DeleteCommand {
    pub fn new(start: usize, end: usize) -> Self {
        Self {
            start,
            end,
            deleted_text: None,
        }
    }
}

impl Command for DeleteCommand {
    fn execute(&mut self, doc: &mut Document) {
        self.deleted_text = Some(doc.delete_range(self.start, self.end));
        doc.set_cursor(self.start);
    }

    fn undo(&mut self, doc: &mut Document) {
        if let Some(ref text) = self.deleted_text {
            doc.insert_at(self.start, text);
            doc.set_cursor(self.start + text.len());
        }
    }

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

/// Replace text command (for find & replace)
#[derive(Debug)]
pub struct ReplaceCommand {
    start: usize,
    end: usize,
    new_text: String,
    old_text: Option<String>,
}

impl ReplaceCommand {
    pub fn new(start: usize, end: usize, new_text: impl Into<String>) -> Self {
        Self {
            start,
            end,
            new_text: new_text.into(),
            old_text: None,
        }
    }
}

impl Command for ReplaceCommand {
    fn execute(&mut self, doc: &mut Document) {
        self.old_text = Some(doc.delete_range(self.start, self.end));
        doc.insert_at(self.start, &self.new_text);
        doc.set_cursor(self.start + self.new_text.len());
    }

    fn undo(&mut self, doc: &mut Document) {
        if let Some(ref old) = self.old_text {
            doc.delete_range(self.start, self.start + self.new_text.len());
            doc.insert_at(self.start, old);
            doc.set_cursor(self.start + old.len());
        }
    }

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

/// Composite command for grouping multiple commands
#[derive(Debug)]
pub struct MacroCommand {
    name: String,
    commands: Vec<Box<dyn Command>>,
}

impl MacroCommand {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            commands: Vec::new(),
        }
    }

    pub fn add(&mut self, command: Box<dyn Command>) {
        self.commands.push(command);
    }
}

impl Command for MacroCommand {
    fn execute(&mut self, doc: &mut Document) {
        for cmd in &mut self.commands {
            cmd.execute(doc);
        }
    }

    fn undo(&mut self, doc: &mut Document) {
        // Undo in reverse order
        for cmd in self.commands.iter_mut().rev() {
            cmd.undo(doc);
        }
    }

    fn description(&self) -> &str {
        &self.name
    }
}

/// Command history manager (Invoker)
pub struct CommandHistory {
    undo_stack: Vec<Box<dyn Command>>,
    redo_stack: Vec<Box<dyn Command>>,
    max_history: usize,
}

impl CommandHistory {
    pub fn new(max_history: usize) -> Self {
        Self {
            undo_stack: Vec::new(),
            redo_stack: Vec::new(),
            max_history,
        }
    }

    pub fn execute(&mut self, mut command: Box<dyn Command>, doc: &mut Document) {
        command.execute(doc);
        self.undo_stack.push(command);
        self.redo_stack.clear(); // Clear redo stack on new command

        // Limit history size
        while self.undo_stack.len() > self.max_history {
            self.undo_stack.remove(0);
        }
    }

    pub fn undo(&mut self, doc: &mut Document) -> Option<&str> {
        if let Some(mut command) = self.undo_stack.pop() {
            command.undo(doc);
            let desc = command.description();
            self.redo_stack.push(command);
            Some(desc)
        } else {
            None
        }
    }

    pub fn redo(&mut self, doc: &mut Document) -> Option<&str> {
        if let Some(mut command) = self.redo_stack.pop() {
            command.execute(doc);
            let desc = command.description();
            self.undo_stack.push(command);
            Some(desc)
        } else {
            None
        }
    }

    pub fn can_undo(&self) -> bool {
        !self.undo_stack.is_empty()
    }

    pub fn can_redo(&self) -> bool {
        !self.redo_stack.is_empty()
    }

    pub fn undo_description(&self) -> Option<&str> {
        self.undo_stack.last().map(|c| c.description())
    }

    pub fn redo_description(&self) -> Option<&str> {
        self.redo_stack.last().map(|c| c.description())
    }
}

/// The text editor (Client)
pub struct TextEditor {
    document: Document,
    history: CommandHistory,
}

impl TextEditor {
    pub fn new() -> Self {
        Self {
            document: Document::new(),
            history: CommandHistory::new(100),
        }
    }

    pub fn insert(&mut self, position: usize, text: &str) {
        let command = Box::new(InsertCommand::new(position, text));
        self.history.execute(command, &mut self.document);
    }

    pub fn delete(&mut self, start: usize, end: usize) {
        let command = Box::new(DeleteCommand::new(start, end));
        self.history.execute(command, &mut self.document);
    }

    pub fn replace(&mut self, start: usize, end: usize, new_text: &str) {
        let command = Box::new(ReplaceCommand::new(start, end, new_text));
        self.history.execute(command, &mut self.document);
    }

    pub fn undo(&mut self) -> bool {
        self.history.undo(&mut self.document).is_some()
    }

    pub fn redo(&mut self) -> bool {
        self.history.redo(&mut self.document).is_some()
    }

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

impl Default for TextEditor {
    fn default() -> Self {
        Self::new()
    }
}

fn main() {
    let mut editor = TextEditor::new();

    // Type some text
    editor.insert(0, "Hello");
    println!("After insert 'Hello': '{}'", editor.content());

    editor.insert(5, " World");
    println!("After insert ' World': '{}'", editor.content());

    editor.insert(11, "!");
    println!("After insert '!': '{}'", editor.content());

    // Undo
    editor.undo();
    println!("After undo: '{}'", editor.content());

    editor.undo();
    println!("After undo: '{}'", editor.content());

    // Redo
    editor.redo();
    println!("After redo: '{}'", editor.content());

    // Replace
    editor.replace(6, 11, "Rust");
    println!("After replace: '{}'", editor.content());

    // Delete
    editor.delete(0, 6);
    println!("After delete: '{}'", editor.content());

    // Undo all
    while editor.undo() {
        println!("Undo: '{}'", editor.content());
    }
}

Real-World Example 2: Database Transaction Commands (Backend)

A transactional command system for database operations.

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

/// Simple in-memory database for demonstration
#[derive(Debug, Clone)]
pub struct Database {
    tables: HashMap<String, Table>,
}

#[derive(Debug, Clone)]
pub struct Table {
    rows: HashMap<u64, Row>,
    next_id: u64,
}

#[derive(Debug, Clone)]
pub struct Row {
    id: u64,
    data: HashMap<String, Value>,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    Null,
    Int(i64),
    Float(f64),
    Text(String),
    Bool(bool),
}

impl Database {
    pub fn new() -> Self {
        Self {
            tables: HashMap::new(),
        }
    }

    pub fn create_table(&mut self, name: &str) {
        self.tables.insert(
            name.to_string(),
            Table {
                rows: HashMap::new(),
                next_id: 1,
            },
        );
    }

    pub fn get_table(&self, name: &str) -> Option<&Table> {
        self.tables.get(name)
    }

    pub fn get_table_mut(&mut self, name: &str) -> Option<&mut Table> {
        self.tables.get_mut(name)
    }
}

impl Default for Database {
    fn default() -> Self {
        Self::new()
    }
}

/// Database command trait
pub trait DbCommand: std::fmt::Debug + Send {
    fn execute(&mut self, db: &mut Database) -> Result<(), String>;
    fn rollback(&mut self, db: &mut Database) -> Result<(), String>;
    fn description(&self) -> String;
}

/// Insert row command
#[derive(Debug)]
pub struct InsertRowCommand {
    table: String,
    data: HashMap<String, Value>,
    inserted_id: Option<u64>,
}

impl InsertRowCommand {
    pub fn new(table: impl Into<String>, data: HashMap<String, Value>) -> Self {
        Self {
            table: table.into(),
            data,
            inserted_id: None,
        }
    }
}

impl DbCommand for InsertRowCommand {
    fn execute(&mut self, db: &mut Database) -> Result<(), String> {
        let table = db
            .get_table_mut(&self.table)
            .ok_or_else(|| format!("Table '{}' not found", self.table))?;

        let id = table.next_id;
        table.next_id += 1;

        table.rows.insert(
            id,
            Row {
                id,
                data: self.data.clone(),
            },
        );

        self.inserted_id = Some(id);
        Ok(())
    }

    fn rollback(&mut self, db: &mut Database) -> Result<(), String> {
        if let Some(id) = self.inserted_id {
            if let Some(table) = db.get_table_mut(&self.table) {
                table.rows.remove(&id);
            }
        }
        Ok(())
    }

    fn description(&self) -> String {
        format!("INSERT INTO {}", self.table)
    }
}

/// Update row command
#[derive(Debug)]
pub struct UpdateRowCommand {
    table: String,
    id: u64,
    new_data: HashMap<String, Value>,
    old_data: Option<HashMap<String, Value>>,
}

impl UpdateRowCommand {
    pub fn new(
        table: impl Into<String>,
        id: u64,
        new_data: HashMap<String, Value>,
    ) -> Self {
        Self {
            table: table.into(),
            id,
            new_data,
            old_data: None,
        }
    }
}

impl DbCommand for UpdateRowCommand {
    fn execute(&mut self, db: &mut Database) -> Result<(), String> {
        let table = db
            .get_table_mut(&self.table)
            .ok_or_else(|| format!("Table '{}' not found", self.table))?;

        let row = table
            .rows
            .get_mut(&self.id)
            .ok_or_else(|| format!("Row {} not found", self.id))?;

        // Save old data for rollback
        self.old_data = Some(row.data.clone());

        // Apply updates
        for (key, value) in &self.new_data {
            row.data.insert(key.clone(), value.clone());
        }

        Ok(())
    }

    fn rollback(&mut self, db: &mut Database) -> Result<(), String> {
        if let Some(ref old_data) = self.old_data {
            if let Some(table) = db.get_table_mut(&self.table) {
                if let Some(row) = table.rows.get_mut(&self.id) {
                    row.data = old_data.clone();
                }
            }
        }
        Ok(())
    }

    fn description(&self) -> String {
        format!("UPDATE {} WHERE id = {}", self.table, self.id)
    }
}

/// Delete row command
#[derive(Debug)]
pub struct DeleteRowCommand {
    table: String,
    id: u64,
    deleted_row: Option<Row>,
}

impl DeleteRowCommand {
    pub fn new(table: impl Into<String>, id: u64) -> Self {
        Self {
            table: table.into(),
            id,
            deleted_row: None,
        }
    }
}

impl DbCommand for DeleteRowCommand {
    fn execute(&mut self, db: &mut Database) -> Result<(), String> {
        let table = db
            .get_table_mut(&self.table)
            .ok_or_else(|| format!("Table '{}' not found", self.table))?;

        self.deleted_row = table.rows.remove(&self.id);

        if self.deleted_row.is_none() {
            return Err(format!("Row {} not found", self.id));
        }

        Ok(())
    }

    fn rollback(&mut self, db: &mut Database) -> Result<(), String> {
        if let Some(ref row) = self.deleted_row {
            if let Some(table) = db.get_table_mut(&self.table) {
                table.rows.insert(row.id, row.clone());
            }
        }
        Ok(())
    }

    fn description(&self) -> String {
        format!("DELETE FROM {} WHERE id = {}", self.table, self.id)
    }
}

/// Transaction manager
pub struct Transaction {
    commands: Vec<Box<dyn DbCommand>>,
    executed: Vec<Box<dyn DbCommand>>,
}

impl Transaction {
    pub fn new() -> Self {
        Self {
            commands: Vec::new(),
            executed: Vec::new(),
        }
    }

    pub fn add(&mut self, command: Box<dyn DbCommand>) {
        self.commands.push(command);
    }

    pub fn commit(&mut self, db: &mut Database) -> Result<(), String> {
        // Execute all commands
        while let Some(mut cmd) = self.commands.pop() {
            match cmd.execute(db) {
                Ok(()) => {
                    println!("Executed: {}", cmd.description());
                    self.executed.push(cmd);
                }
                Err(e) => {
                    // Rollback executed commands on failure
                    self.rollback(db)?;
                    return Err(format!("Transaction failed: {}", e));
                }
            }
        }

        self.executed.clear();
        Ok(())
    }

    pub fn rollback(&mut self, db: &mut Database) -> Result<(), String> {
        // Rollback in reverse order
        while let Some(mut cmd) = self.executed.pop() {
            println!("Rolling back: {}", cmd.description());
            cmd.rollback(db)?;
        }
        Ok(())
    }
}

impl Default for Transaction {
    fn default() -> Self {
        Self::new()
    }
}

fn main() {
    let mut db = Database::new();
    db.create_table("users");

    // Successful transaction
    let mut tx = Transaction::new();

    let mut user_data = HashMap::new();
    user_data.insert("name".to_string(), Value::Text("Alice".to_string()));
    user_data.insert("age".to_string(), Value::Int(30));
    tx.add(Box::new(InsertRowCommand::new("users", user_data)));

    let mut user_data2 = HashMap::new();
    user_data2.insert("name".to_string(), Value::Text("Bob".to_string()));
    user_data2.insert("age".to_string(), Value::Int(25));
    tx.add(Box::new(InsertRowCommand::new("users", user_data2)));

    match tx.commit(&mut db) {
        Ok(()) => println!("Transaction committed successfully"),
        Err(e) => println!("Transaction failed: {}", e),
    }

    println!("Users table: {:?}", db.get_table("users"));

    // Transaction with failure (demonstrating rollback)
    let mut tx2 = Transaction::new();

    let mut update = HashMap::new();
    update.insert("age".to_string(), Value::Int(31));
    tx2.add(Box::new(UpdateRowCommand::new("users", 1, update)));

    // This will fail - non-existent row
    tx2.add(Box::new(DeleteRowCommand::new("users", 999)));

    match tx2.commit(&mut db) {
        Ok(()) => println!("Transaction 2 committed"),
        Err(e) => println!("Transaction 2 failed (expected): {}", e),
    }

    // Original data should be preserved due to rollback
    println!("Users after rollback: {:?}", db.get_table("users"));
}

Real-World Example 3: Game Input Command Queue (Game Development)

A command system for game input with replay support.

use std::collections::VecDeque;
use std::time::Duration;

/// Game state
#[derive(Debug, Clone)]
pub struct GameState {
    player_x: f32,
    player_y: f32,
    health: i32,
    score: u64,
    inventory: Vec<String>,
    frame: u64,
}

impl GameState {
    pub fn new() -> Self {
        Self {
            player_x: 0.0,
            player_y: 0.0,
            health: 100,
            score: 0,
            inventory: Vec::new(),
            frame: 0,
        }
    }
}

impl Default for GameState {
    fn default() -> Self {
        Self::new()
    }
}

/// Input command enum - more efficient than trait objects
#[derive(Debug, Clone)]
pub enum GameCommand {
    Move { dx: f32, dy: f32 },
    Jump { height: f32 },
    Attack { direction: f32 },
    UseItem { item_index: usize },
    PickupItem { item: String },
    DropItem { item_index: usize },
    Save,
    Pause,
}

/// Timestamped command for replay
#[derive(Debug, Clone)]
pub struct TimestampedCommand {
    pub frame: u64,
    pub command: GameCommand,
}

impl GameCommand {
    pub fn execute(&self, state: &mut GameState) -> CommandResult {
        match self {
            GameCommand::Move { dx, dy } => {
                let old_x = state.player_x;
                let old_y = state.player_y;
                state.player_x += dx;
                state.player_y += dy;
                CommandResult::Moved {
                    from: (old_x, old_y),
                    to: (state.player_x, state.player_y),
                }
            }

            GameCommand::Jump { height } => {
                // Simulate jump
                CommandResult::Jumped { height: *height }
            }

            GameCommand::Attack { direction } => {
                // Simulate attack
                state.score += 10;
                CommandResult::Attacked {
                    direction: *direction,
                    hit: true,
                }
            }

            GameCommand::UseItem { item_index } => {
                if *item_index < state.inventory.len() {
                    let item = state.inventory.remove(*item_index);
                    if item == "health_potion" {
                        state.health = (state.health + 25).min(100);
                    }
                    CommandResult::ItemUsed { item }
                } else {
                    CommandResult::Failed {
                        reason: "Invalid item index".to_string(),
                    }
                }
            }

            GameCommand::PickupItem { item } => {
                state.inventory.push(item.clone());
                CommandResult::ItemPickedUp { item: item.clone() }
            }

            GameCommand::DropItem { item_index } => {
                if *item_index < state.inventory.len() {
                    let item = state.inventory.remove(*item_index);
                    CommandResult::ItemDropped { item }
                } else {
                    CommandResult::Failed {
                        reason: "Invalid item index".to_string(),
                    }
                }
            }

            GameCommand::Save => CommandResult::Saved,
            GameCommand::Pause => CommandResult::Paused,
        }
    }
}

#[derive(Debug, Clone)]
pub enum CommandResult {
    Moved { from: (f32, f32), to: (f32, f32) },
    Jumped { height: f32 },
    Attacked { direction: f32, hit: bool },
    ItemUsed { item: String },
    ItemPickedUp { item: String },
    ItemDropped { item: String },
    Saved,
    Paused,
    Failed { reason: String },
}

/// Command queue for processing inputs
pub struct CommandQueue {
    queue: VecDeque<TimestampedCommand>,
    history: Vec<TimestampedCommand>,
    max_history: usize,
}

impl CommandQueue {
    pub fn new(max_history: usize) -> Self {
        Self {
            queue: VecDeque::new(),
            history: Vec::new(),
            max_history,
        }
    }

    pub fn enqueue(&mut self, frame: u64, command: GameCommand) {
        self.queue.push_back(TimestampedCommand { frame, command });
    }

    pub fn process_all(&mut self, state: &mut GameState) -> Vec<CommandResult> {
        let mut results = Vec::new();

        while let Some(ts_cmd) = self.queue.pop_front() {
            let result = ts_cmd.command.execute(state);
            results.push(result);

            // Save to history for replay
            self.history.push(ts_cmd);

            if self.history.len() > self.max_history {
                self.history.remove(0);
            }
        }

        state.frame += 1;
        results
    }

    /// Get recorded commands for replay
    pub fn get_history(&self) -> &[TimestampedCommand] {
        &self.history
    }

    /// Replay commands from history
    pub fn replay(&self, initial_state: GameState) -> Vec<(GameState, CommandResult)> {
        let mut state = initial_state;
        let mut replay = Vec::new();

        for ts_cmd in &self.history {
            let result = ts_cmd.command.execute(&mut state);
            state.frame = ts_cmd.frame;
            replay.push((state.clone(), result));
        }

        replay
    }
}

/// Input buffer with command coalescing
pub struct InputBuffer {
    pending_move: Option<(f32, f32)>,
    pending_commands: Vec<GameCommand>,
}

impl InputBuffer {
    pub fn new() -> Self {
        Self {
            pending_move: None,
            pending_commands: Vec::new(),
        }
    }

    /// Buffer a move command (coalesces multiple moves)
    pub fn buffer_move(&mut self, dx: f32, dy: f32) {
        match &mut self.pending_move {
            Some((x, y)) => {
                *x += dx;
                *y += dy;
            }
            None => {
                self.pending_move = Some((dx, dy));
            }
        }
    }

    /// Buffer other commands
    pub fn buffer_command(&mut self, command: GameCommand) {
        self.pending_commands.push(command);
    }

    /// Flush buffered commands to queue
    pub fn flush(&mut self, frame: u64, queue: &mut CommandQueue) {
        // Flush coalesced move first
        if let Some((dx, dy)) = self.pending_move.take() {
            queue.enqueue(frame, GameCommand::Move { dx, dy });
        }

        // Flush other commands
        for command in self.pending_commands.drain(..) {
            queue.enqueue(frame, command);
        }
    }
}

impl Default for InputBuffer {
    fn default() -> Self {
        Self::new()
    }
}

fn main() {
    let mut state = GameState::new();
    let mut queue = CommandQueue::new(1000);
    let mut input = InputBuffer::new();

    // Simulate game loop
    for frame in 0..10 {
        // Buffer inputs (could come from keyboard, controller, network)
        match frame {
            0 => {
                input.buffer_move(1.0, 0.0);
                input.buffer_move(0.5, 0.0);  // Will be coalesced
            }
            1 => {
                input.buffer_command(GameCommand::PickupItem {
                    item: "health_potion".to_string(),
                });
            }
            3 => {
                input.buffer_command(GameCommand::Attack { direction: 0.0 });
            }
            5 => {
                input.buffer_command(GameCommand::UseItem { item_index: 0 });
            }
            7 => {
                input.buffer_move(0.0, -1.0);
                input.buffer_command(GameCommand::Jump { height: 2.0 });
            }
            _ => {}
        }

        // Flush inputs and process
        input.flush(frame, &mut queue);
        let results = queue.process_all(&mut state);

        for result in results {
            println!("Frame {}: {:?}", frame, result);
        }
    }

    println!("\nFinal state: {:?}", state);

    // Replay demonstration
    println!("\n--- Replay ---");
    let replay = queue.replay(GameState::new());
    for (replay_state, result) in replay {
        println!(
            "Frame {}: pos=({:.1}, {:.1}), hp={}, score={} | {:?}",
            replay_state.frame,
            replay_state.player_x,
            replay_state.player_y,
            replay_state.health,
            replay_state.score,
            result
        );
    }
}

Real-World Example 4: Task Scheduler (DevOps/Automation)

A command-based task scheduler with retries and dependencies.

use std::collections::HashMap;
use std::time::{Duration, Instant};

/// Task status
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TaskStatus {
    Pending,
    Running,
    Completed,
    Failed,
    Cancelled,
}

/// Task command trait
pub trait TaskCommand: std::fmt::Debug + Send + Sync {
    fn name(&self) -> &str;
    fn execute(&self) -> Result<String, String>;
    fn estimated_duration(&self) -> Duration;
}

/// Shell command task
#[derive(Debug)]
pub struct ShellCommand {
    name: String,
    command: String,
    args: Vec<String>,
}

impl ShellCommand {
    pub fn new(name: impl Into<String>, command: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            command: command.into(),
            args: Vec::new(),
        }
    }

    pub fn arg(mut self, arg: impl Into<String>) -> Self {
        self.args.push(arg.into());
        self
    }
}

impl TaskCommand for ShellCommand {
    fn name(&self) -> &str {
        &self.name
    }

    fn execute(&self) -> Result<String, String> {
        // Simulate command execution
        println!("Executing: {} {:?}", self.command, self.args);
        Ok(format!("Output of {}", self.command))
    }

    fn estimated_duration(&self) -> Duration {
        Duration::from_secs(5)
    }
}

/// HTTP request task
#[derive(Debug)]
pub struct HttpRequestCommand {
    name: String,
    url: String,
    method: String,
    body: Option<String>,
}

impl HttpRequestCommand {
    pub fn get(name: impl Into<String>, url: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            url: url.into(),
            method: "GET".to_string(),
            body: None,
        }
    }

    pub fn post(name: impl Into<String>, url: impl Into<String>, body: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            url: url.into(),
            method: "POST".to_string(),
            body: Some(body.into()),
        }
    }
}

impl TaskCommand for HttpRequestCommand {
    fn name(&self) -> &str {
        &self.name
    }

    fn execute(&self) -> Result<String, String> {
        println!("HTTP {} {}", self.method, self.url);
        Ok(format!("Response from {}", self.url))
    }

    fn estimated_duration(&self) -> Duration {
        Duration::from_secs(2)
    }
}

/// Task with retry configuration
pub struct Task {
    command: Box<dyn TaskCommand>,
    retries: u32,
    retry_delay: Duration,
    dependencies: Vec<String>,
    status: TaskStatus,
    attempts: u32,
    output: Option<Result<String, String>>,
}

impl Task {
    pub fn new(command: Box<dyn TaskCommand>) -> Self {
        Self {
            command,
            retries: 0,
            retry_delay: Duration::from_secs(1),
            dependencies: Vec::new(),
            status: TaskStatus::Pending,
            attempts: 0,
            output: None,
        }
    }

    pub fn with_retries(mut self, retries: u32, delay: Duration) -> Self {
        self.retries = retries;
        self.retry_delay = delay;
        self
    }

    pub fn depends_on(mut self, task_name: impl Into<String>) -> Self {
        self.dependencies.push(task_name.into());
        self
    }

    pub fn run(&mut self) -> Result<String, String> {
        self.status = TaskStatus::Running;

        loop {
            self.attempts += 1;
            println!(
                "Running task '{}' (attempt {}/{})",
                self.command.name(),
                self.attempts,
                self.retries + 1
            );

            match self.command.execute() {
                Ok(output) => {
                    self.status = TaskStatus::Completed;
                    self.output = Some(Ok(output.clone()));
                    return Ok(output);
                }
                Err(e) => {
                    if self.attempts <= self.retries {
                        println!(
                            "Task '{}' failed, retrying in {:?}...",
                            self.command.name(),
                            self.retry_delay
                        );
                        std::thread::sleep(self.retry_delay);
                    } else {
                        self.status = TaskStatus::Failed;
                        self.output = Some(Err(e.clone()));
                        return Err(e);
                    }
                }
            }
        }
    }
}

/// Task scheduler
pub struct Scheduler {
    tasks: HashMap<String, Task>,
    execution_order: Vec<String>,
}

impl Scheduler {
    pub fn new() -> Self {
        Self {
            tasks: HashMap::new(),
            execution_order: Vec::new(),
        }
    }

    pub fn add_task(&mut self, task: Task) {
        let name = task.command.name().to_string();
        self.tasks.insert(name.clone(), task);
        self.execution_order.push(name);
    }

    pub fn run_all(&mut self) -> HashMap<String, Result<String, String>> {
        let mut results = HashMap::new();
        let mut completed = std::collections::HashSet::new();

        // Topological sort would be better, but this works for simple cases
        let order = self.execution_order.clone();

        for task_name in order {
            // Check dependencies
            if let Some(task) = self.tasks.get(&task_name) {
                let deps_satisfied = task
                    .dependencies
                    .iter()
                    .all(|dep| completed.contains(dep));

                if !deps_satisfied {
                    println!("Skipping '{}': dependencies not satisfied", task_name);
                    continue;
                }
            }

            if let Some(task) = self.tasks.get_mut(&task_name) {
                let result = task.run();
                if result.is_ok() {
                    completed.insert(task_name.clone());
                }
                results.insert(task_name, result);
            }
        }

        results
    }
}

impl Default for Scheduler {
    fn default() -> Self {
        Self::new()
    }
}

fn main() {
    let mut scheduler = Scheduler::new();

    // Define tasks with dependencies
    scheduler.add_task(Task::new(Box::new(
        ShellCommand::new("checkout", "git")
            .arg("checkout")
            .arg("main"),
    )));

    scheduler.add_task(
        Task::new(Box::new(ShellCommand::new("build", "cargo").arg("build")))
            .depends_on("checkout")
            .with_retries(2, Duration::from_secs(1)),
    );

    scheduler.add_task(
        Task::new(Box::new(ShellCommand::new("test", "cargo").arg("test")))
            .depends_on("build"),
    );

    scheduler.add_task(
        Task::new(Box::new(HttpRequestCommand::post(
            "deploy",
            "https://api.deploy.com/trigger",
            r#"{"env": "production"}"#,
        )))
        .depends_on("test")
        .with_retries(3, Duration::from_secs(5)),
    );

    // Run all tasks
    println!("=== Starting Task Scheduler ===\n");
    let results = scheduler.run_all();

    println!("\n=== Results ===");
    for (name, result) in results {
        match result {
            Ok(output) => println!("{}: SUCCESS - {}", name, output),
            Err(e) => println!("{}: FAILED - {}", name, e),
        }
    }
}

Comparison: Command Implementation Approaches

| Approach | Flexibility | Performance | Type Safety | Use Case |

|----------|-------------|-------------|-------------|----------|

| Trait objects | High | Medium | High | Complex commands, plugins |

| Enums | Medium | High | Highest | Fixed set of commands |

| Closures | High | High | Medium | Simple callbacks |

| Function pointers | Low | Highest | Low | C interop, hot paths |

⚠️ Anti-patterns

// DON'T: Commands with hidden state dependencies
struct BadCommand {
    // No way to know what external state this modifies
}

impl BadCommand {
    fn execute(&self) {
        // Modifies global state secretly
        unsafe { GLOBAL_COUNTER += 1; }
    }
}

// DON'T: Mutable receiver for undo
struct BadUndoCommand {
    data: String,
}

impl BadUndoCommand {
    // Can't undo if data is consumed
    fn execute(self) -> String {
        self.data
    }
}

// DO: Commands own their undo state
struct GoodCommand {
    new_value: String,
    old_value: Option<String>,  // Captured during execute
}

Best Practices

  1. Commands should be self-contained - Include all necessary data
  2. Undo state captured during execute - Not before
  3. Use enums for fixed command sets - Better performance and type safety
  4. Consider serialization - Commands often need to be saved/transmitted
  5. Log command execution - Useful for debugging and auditing
  6. Handle failures gracefully - Commands should be atomic when possible

Performance Characteristics

| Operation | Trait Object | Enum | Closure |

|-----------|-------------|------|---------|

| Dispatch | Virtual call | Match | Indirect call |

| Memory | Heap + vtable | Stack | Heap (if boxed) |

| Inline | No | Yes | Sometimes |

| Clone | Requires Clone | Easy | Usually not |

Exercises

Beginner

  1. Create a LightSwitch with TurnOn and TurnOff commands
  2. Implement a simple calculator with command history

Intermediate

  1. Build a file system simulator with Create, Delete, Move commands
  2. Implement a paint program with brush stroke commands and undo

Advanced

  1. Create a distributed command queue with serialization
  2. Implement a SQL query builder using the command pattern

Further Reading

Real-World Usage

  • undo/redo: Text editors, graphics software, IDEs
  • git: Each commit is essentially a command
  • Redux: Actions are command objects
  • CI/CD pipelines: Build steps as commands
  • Game engines: Input systems, replay functionality

🎮 Try it Yourself

🎮

Command Pattern - Playground

Run this code in the official Rust Playground