Variables and mutability

Rust · Essentials · 8 min

Immutability by default is the first place Rust pushes you toward code that is easier to reason about.

let introduces a binding. Bindings are immutable by default: once let x = 5; runs, assigning x = 6 is a compile error. To allow changes, say so with let mut x = 5;. This makes every place that mutates state visible at the declaration.

Types are inferred from usage, so annotations are optional in most local code: let count: u32 = 0; is the same as let count = 0u32;. When inference cannot decide (for example parsing a string), the compiler asks you to annotate.

Shadowing means declaring a new variable with the same name: let x = x + 1;. Unlike mut, it creates a fresh binding, so you can even change the type, which is handy for transforming input step by step (let input = input.trim();). The old value still exists until the end of its scope but is no longer reachable by that name.

const declares a compile-time constant. It always needs a type, is written in SCREAMING_SNAKE_CASE, and can live at module level: const MAX_USERS: usize = 1_000;. A static is similar but has one fixed memory address for the whole program; prefer const unless you need that address.

A binding must be initialised before use, but not necessarily at the let: the compiler tracks definite initialisation, so let x; if c { x = 1 } else { x = 2 } is fine. Unused variables produce warnings; prefix them with _ to silence the warning on purpose.

Example

const MAX_RETRIES: u32 = 3;

fn main() {
    let attempts = 0;
    // attempts += 1;          // error: cannot assign twice to immutable variable
    let mut tries = attempts;
    tries += 1;

    let input = "  42  ";
    let input = input.trim();              // shadow: still &str
    let input: i32 = input.parse().unwrap(); // shadow: now i32

    println!("{tries}/{MAX_RETRIES}, value = {input}"); // 1/3, value = 42
}

Remember

  • Bindings are immutable unless declared let mut
  • Shadowing creates a new binding and may change the type
  • const needs a type annotation and is evaluated at compile time

Watch out for

  • Reaching for mut when shadowing (a transformed, immutable value) expresses the intent better.

Check yourself

  1. What is the key difference between `let x = x * 2;` (shadowing) and `x = x * 2;` on a `let mut x`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: doc.rust-lang.org/book/ch03-01-variables-and-mutability.html