Course contentsShow
← Rust
Lesson 1118 of 1,91010. Concurrency PrimitivesFree lesson

The Actor Model in Rust

You will encapsulate state inside thread-isolated actors that interact exclusively through messaging.

In this lesson, you will learn how to encapsulate mutable state inside isolated threads—"actors"—that communicate exclusively by passing messages rather than sharing memory.

The Actor Pattern

When multiple threads share memory via Mutexes or RwLocks, you face the constant risk of deadlocks and complex lock-ordering requirements. The Actor Model flips this strategy: instead of sharing the data, you give each piece of state its own dedicated "owner" thread.

An actor consists of three things:

  1. Private State: Data that is never accessed by other threads.
  2. A Mailbox: A channel (like an mpsc channel) used to receive instructions.
  3. The Loop: A continuous task that processes messages and updates the state.

Because the state is entirely contained within the actor’s thread, you don't need locks. The thread processing the loop is the only entity that can mutate the state, effectively turning concurrency into a sequence of serialized message processing events.

Implementation Pattern

Here is how you structure a simple counter actor:

use std::sync::mpsc;
use std::thread;

enum Message {
    Increment,
    Query(mpsc::Sender<i32>),
}

fn spawn_counter() -> mpsc::Sender<Message> {
    let (tx, rx) = mpsc::channel();
    
    thread::spawn(move || {
        let mut count = 0; // State is isolated here
        while let Ok(msg) = rx.recv() {
            match msg {
                Message::Increment => count += 1,
                Message::Query(reply_tx) => {
                    let _ = reply_tx.send(count);
                }
            }
        }
    });
    
    tx
}

By sending a Message::Increment to the returned tx handle, you request a change without needing to know how the count is stored. If you need a return value, you include a "one-shot" channel in the message, allowing the actor to reply asynchronously. This decouples the requester from the implementation details of the state, ensuring that data races are physically impossible because no memory is shared between threads.

Key Takeaway: The Actor Model replaces shared-memory synchronization with message passing, ensuring that state is only ever mutated by the thread that owns it.