Variables & Data Types

Variables in Rust are immutable by default — a genuine surprise if you're coming from almost any other language on this site. Once you bind a value with let, you can't change it unless you say so explicitly with mut.

let is immutable unless you say mut

Rust src/main.rs
fn main() {
    let score = 88;
    println!("Score: {}", score);

    score = 92;
    println!("Score: {}", score);
}
Compiler output
error[E0384]: cannot assign twice to immutable variable `score`
 --> src/main.rs:5:5
  |
2 |     let score = 88;
  |         -----
  |         |
  |         first assignment to `score`
  |         help: consider making this binding mutable: `mut score`
...
5 |     score = 92;
  |     ^^^^^^^^^^^ cannot assign twice to immutable variable

This isn't a warning — it's a compile error, and the program never runs. Rust defaults to immutable on purpose: most variables never actually need to change after they're set, and marking the few that do makes those spots easy to spot when reading code later. Fix it by adding mut:

Rust src/main.rs
fn main() {
    let mut score = 88;
    println!("Score: {}", score);

    score = 92;
    println!("Score: {}", score);
}
Output
Score: 88
Score: 92

Basic types

Rust infers types most of the time, but every value still has one, decided at compile time — nothing changes at runtime:

Rust src/main.rs
fn main() {
    let age: i32 = 30;         // signed 32-bit integer
    let price: f64 = 19.99;    // 64-bit float
    let initial: char = 'R';    // a single character
    let active: bool = true;
    let name = "Priya";          // inferred as &str

    println!("{} is {}, active: {}", name, age, active);
}
Output
Priya is 30, active: true

i32 is the default integer type if you don't specify one, and f64 the default float type. "Priya"'s type is &str — a reference to string data — rather than a full-blown String; the difference between the two matters a lot once you reach ownership, and gets its own note there.

Shadowing

Rust also lets you declare a new variable with the same name as an old one — shadowing it — which is different from mutation:

Rust src/main.rs
fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
    println!("{}", spaces);
}
Output
3
Note: shadowing with a second let creates a brand-new binding — it can even change type, as it does above (from &str to usize). This is different from mut, which keeps the same variable and the same type but allows its value to change. Reach for mut when a value genuinely changes over time; reach for shadowing when you're transforming a value once and don't need the old form anymore.