Immutable vs const in Rust: Why Even Experienced Developers Get Confused?
\n\nHave you ever written code in Rust and suddenly thought: "Wait, I declared a variable with let, it's immutable. And const is also kind of a constant. So what's the difference?". If so — you are not alone. The question "What is the difference between immutable and const variables in Rust?" on Stack Overflow has thousands of views. And this is no coincidence.
At first glance, it seems that both let x = 5 and const X: i32 = 5 do the same thing: they prevent changing the value. But in Rust, a language where memory safety and performance are paramount, the devil is in the details. In 2025, understanding these details is not just theory, but a necessity for writing efficient and idiomatic code.
In this article, we will once and for all figure out: how is an immutable variable different from a constant, when to use what, and what best practices are relevant right now. Let's go!
\n\n1. Basic Understanding: What Does the Compiler Say?
\n\nLet's start with the simplest. In Rust, variables are immutable by default. This means that after assigning a value, you cannot overwrite it.
\n\nfn main() {\n let x = 5;\n // x = 6; // Error! Cannot change an immutable variable\n println!("x = {}", x);\n}\n\nAnd here's an example with a constant:
\n\nconst MAX_POINTS: u32 = 100_000;\n\nfn main() {\n println!("MAX_POINTS = {}", MAX_POINTS);\n}\n\nVisually — it looks similar. But the Rust compiler handles them completely differently. And this is where the interesting part begins.
\n\n1.1. Key Difference #1: Where Do They Live?
\n\n- \n
let— declares a variable. It exists on the stack (or heap, if it's aBox,String, etc.) and has a memory address. You can take a reference to it, pass it to functions, move it. \n const— declares a constant. It does not have a fixed memory address. The compiler inlines its value directly into the code at the point of use. \n
This is a fundamental difference. const is not a variable; it's a named value that is substituted at compile time.
fn main() {\n let x = 42;\n let y = &x; // Ok, taking a reference to a variable\n\n const Z: i32 = 100;\n // let w = &Z; // Error! A constant has no address, cannot take a reference\n}\n\n2. Deep Dive: Performance and Semantics
\n\nNow that we know about the "place of residence", let's break down other important aspects.
\n\n2.1. Lifetime and Scope
\nConstants live in the global scope (or inside a function, but with a static lifetime). They exist for the entire duration of the program. They can be declared outside a function and will be accessible everywhere.
Immutable variables only live inside a block (function, loop, if). Once the block ends, the memory is freed.
const GLOBAL_CONST: i32 = 10;\n\nfn main() {\n let local_var = 20;\n // local_var only lives inside main\n}\n// local_var no longer exists\n// GLOBAL_CONST is still accessible\n\n2.2. Type and Annotations
\nFor const, specifying the data type is mandatory. For let, the compiler will infer it.
const PI: f64 = 3.14159; // Mandatory: const NAME: Type = value;\nlet pi = 3.14159; // The compiler will figure out it's f64\n\n2.3. Compile-Time Evaluation (CTFE)
\nThe value of const must be computable at compile time.