Rust Variables

Rust's `let` keyword binds values to names, and unlike most languages, those bindings are immutable unless you explicitly opt in to change.

Declaring Variables with let

Every variable in Rust starts life with the `let` keyword. You don't need to declare a type up front in most cases -- the compiler infers it from the value you assign, and from how you use the variable afterward.

Basic Variable Declaration

fn main() {
    let language = "Rust";
    let year = 2015;
    println!("{} was first released around {}", language, year);
}

Immutability by Default

Once a value is bound with `let`, Rust will not let you change it. This is a deliberate design choice: immutable data is easier to reason about, safer to share across threads, and it eliminates a whole category of bugs where a variable changes value somewhere you didn't expect. Trying to reassign `language` above would fail to compile with an error pointing at the exact line.

Note: The compiler error for reassigning an immutable variable -- "cannot assign twice to immutable variable" -- appears at build time, not at runtime. You find out about the mistake before the program ever ships.

Opting Into Mutability with mut

When you genuinely need a variable to change -- a loop counter, a running total, a value read from user input -- add the `mut` keyword after `let`. This tells both the compiler and anyone reading the code that this binding is expected to change.

Using mut to Update a Value

fn main() {
    let mut score = 0;
    println!("Starting score: {}", score);

    score = score + 10;
    score += 5;

    println!("Final score: {}", score);
}

Shadowing: Rebinding Instead of Mutating

Rust also lets you declare a new variable with the same name as a previous one, using `let` again. This is called shadowing. Unlike `mut`, shadowing creates an entirely new binding -- which means the new value can even have a different type than the old one.

Shadowing to Change Type

fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
    println!("That string had {} spaces", spaces);
}
  • `let` creates an immutable binding by default
  • `let mut` allows the same variable to be reassigned, but only to a value of the same type
  • Shadowing (`let` again) creates a brand-new variable, so the type is free to change
  • Shadowed variables are only visible from the point of shadowing onward within their scope
Note: Shadowing is scoped. If you shadow a variable inside a `{ }` block, the outer variable reappears unchanged once that block ends -- the shadow only applies inside it.
ApproachReassignable?Can change type?Keyword
`let`NoNolet
`let mut`YesNolet mut
ShadowingN/A -- new bindingYeslet (again)

Exercise: Rust Variables

By default, once a value is bound with `let`, can it be reassigned?