Why doesn't the compiler report an error when a variable not declared as mutable is modified?

What you have written is identical to:

let x: u32 = 10;

The compiler will not permit you to mutate it thereafter:

let x: u32;
x = 10;
x = 0; // Error: re-assignment of immutable variable `x`

Note that it is a compiler error if you try to use an uninitialized variable:

let x: u32;
println!("{}", x); // Error: use of possibly uninitialized variable: `x`

This feature can be pretty useful if you want to initialize the variable differently based on runtime conditions. A naive example:

let x: u32;
if condition {
    x = 1;   
} else if other_condition {
    x = 10;
} else {
    x = 100;
}

But still it will still be an error if there is a possibility that it isn't initialized:

let x: u32;
if condition {
    x = 1;   
} else if other_condition {
    x = 10;
} // no else
println!("{:?}", x); // Error: use of possibly uninitialized variable: `x`

As mentioned, this is not mutation, but deferred initialization:

  • mutation is about changing the value of an existing variable,
  • deferred initialization is about declaring a variable at one point, and initializing it later.

The Rust compiler tracks whether a variable has a value at compile-time, so unlike C there is no risk of accidentally using an uninitialized variable (or unlike C++, a variable that was moved from).


The most important reason for using deferred initialization is scope.

fn main() {
    let x;
    let mut v = vec!();

    {
        x = 2;
        v.push(&x);
    }

    println!("{:?}", v);
}

In Rust, the borrow-checker will validate that a reference cannot outlive the value it refers to, preventing dangling references.

This means that v.push(&x) requires that x lives longer than v, and therefore be declared before v.

The need for it does not crop up often, but when it does other solutions would require run-time checks.