Course contentsShow
← Rust
Lesson 290 of 1,9103. Borrowing and ReferencesFree lesson

Shared Immutable State Patterns

Design safe programs using shared read-only data structures.

In this lesson, you will learn how to design systems that allow multiple parts of your program to safely inspect the same data simultaneously without ownership conflicts.

The Problem of Exclusive Access

In Rust, the borrow checker enforces a simple but strict rule: you can have either one mutable reference or any number of immutable references to a value. When you need a configuration object or a lookup table to be accessible across different parts of your application—like multiple functions, modules, or even background threads—passing ownership becomes impossible because every component needs access to the same state.

Shared Immutable State

To solve this, we rely on the pattern of Shared Immutable State. By making data immutable, we bypass the dangers of "data races," where one part of the code changes a value while another is reading it. Since the data never changes, Rust allows us to create as many shared references to that data as we need.

Consider a shared configuration object:

struct Config {
    api_url: String,
    timeout: u64,
}

fn print_status(config: &Config) {
    println!("Connecting to {}...", config.api_url);
}

fn main() {
    let app_config = Config {
        api_url: "https://api.example.com".to_string(),
        timeout: 30,
    };

    // We can pass the same immutable reference to multiple locations
    print_status(&app_config);
    print_status(&app_config);
}

In this pattern, app_config acts as the "source of truth." Because we only provide immutable references (&Config) to the functions, we guarantee that those functions cannot modify the state. This creates a predictable flow of data throughout your application.

Design Principles

When designing for shared immutable state, focus on these two points:

  1. Encapsulation: Keep the state in a central location (like main or a dedicated manager struct) and pass references downward to the components that need to read it.
  2. Read-Only Contracts: If a component only needs to read information, always define your function signatures to accept immutable references (&T) rather than taking ownership of the value. This ensures your API clearly signals that the data is protected from modification.

Key Takeaway: Shared immutable state uses the compiler's borrow rules to enable safe, widespread access to data by ensuring that as long as state cannot be changed, it can be shared freely across your entire program.