Skip to content

Core Concept: Variables & Types ​

Updated Mar 2026

Overview ​

Rust is statically typed with powerful type inference. Variables are immutable by default, shadowing replaces mutation for type changes, and the type system catches errors at compile time.

Immutability by Default ​

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

    let mut y = 5;
    y = 6;  // OK: y is mutable

    // Constants β€” must annotate type, must be compile-time evaluable
    const MAX_POINTS: u32 = 100_000;

    // Static β€” global lifetime, fixed memory address
    static LANGUAGE: &str = "Rust";
}

Shadowing ​

Shadowing is NOT mutation β€” it creates a new variable that happens to have the same name.

rust
fn main() {
    let x = 5;
    let x = x + 1;        // New x shadows old x (now 6)
    let x = x * 2;        // New x shadows again (now 12)

    // Shadowing can change types!
    let spaces = "   ";          // &str
    let spaces = spaces.len();   // usize β€” different type, same name

    // mut CANNOT change types
    // let mut spaces = "   ";
    // spaces = spaces.len();    // ERROR: mismatched types
}

Scalar Types ​

Integers ​

rust
fn main() {
    // Signed integers
    let a: i8 = -128;           // -128 to 127
    let b: i16 = -32_768;
    let c: i32 = 42;            // Default integer type
    let d: i64 = 1_000_000;
    let e: i128 = 170_141_183;
    let f: isize = 42;          // Pointer-sized (64-bit on 64-bit systems)

    // Unsigned integers
    let g: u8 = 255;
    let h: u16 = 65_535;
    let i: u32 = 4_294_967_295;
    let j: u64 = 42;
    let k: u128 = 340_282_366;
    let l: usize = 42;          // For array indexing

    // Literal forms
    let decimal = 98_222;
    let hex = 0xff;
    let octal = 0o77;
    let binary = 0b1111_0000;
    let byte = b'A';            // u8 only

    // Overflow handling
    let x: u8 = 255;
    // let y: u8 = x + 1;      // Panics in debug, wraps in release
    let y = x.wrapping_add(1);  // Always wraps: 0
    let y = x.checked_add(1);   // Returns None on overflow
    let y = x.saturating_add(1); // Clamps at max: 255
    let (y, overflow) = x.overflowing_add(1); // (0, true)
}

Floats ​

rust
fn main() {
    let x = 2.0;      // f64 (default)
    let y: f32 = 3.0;  // f32

    // Floating-point comparison β€” NEVER use ==
    let a = 0.1 + 0.2;
    // assert_eq!(a, 0.3);  // FAILS! Floating point imprecision
    assert!((a - 0.3).abs() < f64::EPSILON);  // Correct

    // Special values
    let inf = f64::INFINITY;
    let neg_inf = f64::NEG_INFINITY;
    let nan = f64::NAN;
    assert!(nan.is_nan());
    assert!(nan != nan);  // NaN is not equal to itself!
}

Boolean and Character ​

rust
fn main() {
    let t: bool = true;
    let f: bool = false;

    // char is 4 bytes (Unicode scalar value)
    let c: char = 'z';
    let emoji: char = 'πŸ¦€';
    let chinese: char = 'δΈ­';
    assert_eq!(std::mem::size_of::<char>(), 4);
}

Compound Types ​

Tuples ​

rust
fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    // Destructuring
    let (x, y, z) = tup;

    // Index access
    let five_hundred = tup.0;
    let six_point_four = tup.1;

    // Unit tuple β€” zero-size type
    let unit: () = ();  // Functions that return nothing return ()

    // Functions can return tuples
    fn swap(a: i32, b: i32) -> (i32, i32) {
        (b, a)
    }
}

Arrays ​

rust
fn main() {
    // Fixed-size, stack-allocated
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    let zeros = [0; 10];  // [0, 0, ..., 0] (10 elements)

    let first = arr[0];
    let slice = &arr[1..3];  // &[i32] slice: [2, 3]

    // Runtime bounds checking
    // let oob = arr[10];  // Panics at runtime!
    let safe = arr.get(10);  // Returns None
}

Type Inference and Turbofish ​

rust
fn main() {
    // Rust infers types when possible
    let x = 42;        // i32 (default)
    let y = 3.14;      // f64 (default)
    let s = "hello";   // &str

    // Turbofish syntax for explicit type on generic calls
    let parsed = "42".parse::<i32>().unwrap();
    let numbers: Vec<i32> = vec![1, 2, 3];
    let collected = (0..5).collect::<Vec<_>>();
}

Type Aliases ​

rust
// Simplify complex types
type Thunk = Box<dyn Fn() + Send + 'static>;
type Result<T> = std::result::Result<T, std::io::Error>;

fn takes_long_type(f: Thunk) { }
fn returns_result() -> Result<()> { Ok(()) }

Sizes and Alignment ​

rust
use std::mem;

fn main() {
    println!("bool: {} bytes", mem::size_of::<bool>());    // 1
    println!("char: {} bytes", mem::size_of::<char>());    // 4
    println!("i32: {} bytes", mem::size_of::<i32>());      // 4
    println!("f64: {} bytes", mem::size_of::<f64>());      // 8
    println!("&str: {} bytes", mem::size_of::<&str>());    // 16 (ptr + len)
    println!("String: {} bytes", mem::size_of::<String>()); // 24 (ptr + len + cap)
    println!("Vec<i32>: {} bytes", mem::size_of::<Vec<i32>>()); // 24
    println!("Option<i32>: {} bytes", mem::size_of::<Option<i32>>()); // 8
    println!("Option<&i32>: {} bytes", mem::size_of::<Option<&i32>>()); // 8 (niche opt!)
}

Built with VitePress | Rust SOP Documentation