RsRust · Lesson 2 of 8

Variables & Types

In Rust, variables are immutable by default. This is not a bug. This is the entire point.

Use let to declare variables. They are immutable by default — you cannot change them after assignment. Use let mut if you need mutability. Rust infers types, but you can annotate them explicitly.

Rust
fn main() {
    // Immutable by default
    let x = 5;
    // x = 6;  // ERROR: cannot assign twice to immutable variable

    // Mutable
    let mut y = 5;
    y = 6;   // OK
    println!("y = {}", y);

    // Explicit type annotation
    let z: i32 = 42;
    let pi: f64 = 3.14159;
    let flag: bool = true;
    let ch: char = 'A';

    // Integer types
    let a: i8  = 127;         // signed 8-bit:  -128 to 127
    let b: i32 = 2_147_483_647;  // signed 32-bit (underscores for readability)
    let c: u64 = 18_446_744_073_709_551_615; // unsigned 64-bit
    let d: usize = 42;        // pointer-sized int (use for indexing)

    // Float types
    let e: f32 = 3.14;    // 32-bit float
    let f: f64 = 3.14;    // 64-bit float (default for float literals)

    // Constants — must have explicit type, evaluated at compile time
    const MAX_POINTS: u32 = 100_000;
}

Shadowing is a Rust superpower: you can re-declare a variable with the same name using let, even changing its type. This is different from mutation.

Rust
fn main() {
    // Shadowing — each let creates a new variable
    let spaces = "   ";                 // &str
    let spaces = spaces.len();          // usize — different type!
    println!("{}", spaces);             // 3

    // String types
    let s1 = "hello";           // &str — string slice, lives in program data
    let s2 = String::from("hello");  // String — heap-allocated, growable

    let mut s3 = String::from("hello");
    s3.push_str(", world");     // mutate a String
    s3.push('!');               // append a char
    println!("{}", s3);         // hello, world!

    // Tuples — fixed-size collection of mixed types
    let tup: (i32, f64, bool) = (500, 6.4, true);
    let (x, y, z) = tup;       // destructuring
    println!("{} {} {}", x, y, z);
    println!("{}", tup.0);      // access by index

    // Arrays — fixed-size, same type
    let arr = [1, 2, 3, 4, 5];
    let zeros = [0; 10];        // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    println!("{}", arr[0]);     // 1
}
⚠ Warning
Integer overflow panics in debug mode and wraps silently in release mode. Use checked_add(), saturating_add(), or wrapping_add() when you need explicit overflow behavior.