#3 Variables and types
let, mut, const, types
Immutable by default
In Rust, variables are immutable by default. Once you bind a value to a name, you cannot change it. This is one of Rust's key safety features — it helps prevent accidental modifications.
let x = 5;
println!("x = {x}");
// x = 6; // ERROR: cannot assign twice to immutable variableThe let keyword declares a variable. If you try to reassign it, the compiler will stop you with a clear error message.
Mutable variables (mut)
When you need a variable that can change, add the mut keyword:
let mut x = 5;
println!("x = {x}");
x = 6;
println!("x = {x}");The mut keyword signals your intent clearly: this value will change. It makes your code easier to reason about because mutability is always explicit.
Constants
Constants are values that are always immutable. Unlike let variables, constants must have their type annotated and can be declared in any scope, including global scope:
const MAX_POINTS: u32 = 100_000;
println!("MAX_POINTS = {MAX_POINTS}");
// Constants must be known at compile time
// They cannot use `mut` and must have a type annotationBy convention, constants use SCREAMING_SNAKE_CASE. The underscores in numbers (100_000) are visual separators that make large numbers easier to read.
Basic types
Rust is statically typed — every value has a type known at compile time. Here are the fundamental types:
let a: i32 = 42; // Signed 32-bit integer (default)
let b: u8 = 255; // Unsigned 8-bit integer
let c: i64 = -1000; // Signed 64-bit integer
let d: usize = 10; // Pointer-sized unsigned integerlet pi: f64 = 3.14; // 64-bit float (default)
let active: bool = true; // Boolean
let grade: char = 'A'; // Unicode character
let hello: &str = "Hello, Rust!"; // String sliceRust can often infer the type from context, so you don't always need to write the annotation. But being explicit helps readability, especially for beginners.
Shadowing
Rust allows you to declare a new variable with the same name as a previous one. The new variable shadows the old one:
let x = 5;
let x = x + 1; // x is now 6
let x = x * 2; // x is now 12
// You can even change the type!
let spaces = " "; // &str
let spaces = spaces.len(); // usizeShadowing is different from mut: you create a brand new variable each time. This lets you reuse names and even change types.
Your turn
Try running the variables demo with cargo run and cargo check in the terminal below: