Variables, Mutability & Shadowing
let creates bindings. Immutability is the default; mut opts into reassignment. Shadowing lets you reuse a name with a new type or value without making everything mutable.
Search across all documentation pages
let creates bindings. Immutability is the default; mut opts into reassignment. Shadowing lets you reuse a name with a new type or value without making everything mutable.
const MAX_RETRIES: u32 = 3;
fn main() {
let input = "42";
let input: u32 = input.parse().unwrap();
let mut attempts = 0;
attempts += 1;
println!("parsed {input}, attempt {attempts}, max {MAX_RETRIES}");
}When to reach for this: Any time you need clear, local state. Prefer immutable let bindings and shadowing over sprinkling mut everywhere.
const DEFAULT_PORT: u16 = 8080;
fn parse_port(raw: &str) -> u16 {
let raw = raw.trim();
let port: u16 = raw.parse().expect("port must be a number");
port
}
fn main() {
let mut log_level = "info";
log_level = "debug";
let port = parse_port(" 3000 ");
println!("port={port}, log={log_level}, default={DEFAULT_PORT}");
}What this demonstrates:
const for compile-time constants with explicit typesraw after trimming without mutmut only where reassignment is needed (log_level)let introduces a new binding in the current scopex = 1 fails)mut allows reassignment of the binding itselflet vs const vs static| Form | Evaluated | Type required | Use |
|---|---|---|---|
let | Runtime | Inferred or annotated | Local variables |
const | Compile time | Required | Module-level constants |
static | Runtime (fixed address) | Required | Global singletons, FFI |
const KB: u32 = 1024;
static APP_NAME: &str = "myapp";Shadowing is common in parser chains:
let spaces = " ";
let spaces = spaces.len(); // usize now, not &strUnlike mut, shadowing can change the type because each let is a new binding.
let (x, y) = (1, 2);
let [first, ..] = [10, 20, 30];Destructuring in let bindings unpacks tuples, arrays, and structs in one step.
mut when shadowing suffices - Parsing pipelines become harder to follow. Fix: Shadow with let parsed = input.parse()? instead of mutating a string buffer.let x = Rc::new(RefCell::new(0)) needs no mut on x to call borrow_mut. Fix: Understand that mut is about the binding, not heap contents.const cannot use String::from at runtime. Fix: Use const for literals; use static or lazy_static/OnceLock for runtime init.let _ = expensive(); still runs the expression. Fix: Prefix with _ only when the side effect or drop is intended.
| Alternative | Use When | Don't Use When |
|---|---|---|
Shadowing (let reuse) | Parse/transform pipelines, type changes | Long functions where name reuse confuses readers |
mut binding | Loop counters, incremental builders | Most local values that are set once |
const | Magic numbers, fixed limits | Values computed at runtime from config |
Cell/RefCell | Shared mutation behind immutable binding | You can use &mut with exclusive access |
It makes data flow easier to reason about and enables the borrow checker to prove safety. Mutation is explicit via mut.
Yes. let mut x = 1; let x = 2; creates a new immutable binding that hides the mutable one.
const values are inlined at compile time. They do not occupy stack space as variables at runtime.
static has a fixed memory address for the program lifetime. const is a compile-time value copied where used.
Yes. pub const TIMEOUT_MS: u64 = 5000; is common in library crates.
No. Shadowing creates a new binding. Reassignment (mut) updates the same binding.
Yes. let Point { x, y } = point; and let Some(v) = opt else { return }; (let-else) are idiomatic.
Even unused bindings can move ownership unless the type is Copy. Use _ prefix if you intentionally ignore a moved value.
Yes, but it only allows reassigning the local parameter binding. It does not affect the caller.
Avoid static mut. Prefer OnceLock, Mutex, or const data. static mut requires unsafe to read/write.
letStack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Reviewed by Chris St. John·Last updated Jul 19, 2026