Question
In Rust, variables are immutable by default unless they are declared with mut, which means their value cannot be changed after assignment.
If that is already the default behavior, what is the purpose of the const keyword? Are immutable variables and constants the same thing, or do they serve different roles in Rust? If they are different, how do they differ in practice?
Short Answer
By the end of this page, you will understand that Rust immutable variables and const items are not the same thing, even though neither can be changed after creation. You will learn how they differ in when values are computed, where they can be used, scope and lifetime, type requirements, and common real-world usage patterns.
Concept
Rust has two related but different ideas:
- Immutable variables: values bound to a name that cannot be reassigned
- Constants (
const): named values that are fixed at compile time
An immutable variable is created with let:
let x = 10;
This means:
xcannot be reassignedxis still a regular local variable binding- its value can come from a runtime expression
A constant is created with const:
const MAX_USERS: u32 = 100;
This means:
- the value is known at compile time
- the type must be explicitly written
- it can be declared in broader scopes, including module scope and global-like contexts
- it is intended for fixed values that never change
Why this matters
In real Rust programs, you often need both:
- immutable variables for ordinary values inside functions
- constants for shared configuration-like values such as limits, sizes, mathematical values, and protocol settings
The key distinction is not just “can it change?” but also:
- when the value is determined
- what places in the code can use it
- what kind of expression is allowed
Important idea
This is valid immutable binding code:
let x = some_function();
If some_function() runs at runtime, x is still fine.
But a const must be computable at compile time:
const X: i32 = 5 + 5;
This is okay because the compiler can fully evaluate it ahead of time.
So the short answer is:
- immutable
let= a normal variable that cannot be reassigned const= a compile-time constant with stricter rules and wider usage
Mental Model
Think of these as two different kinds of labels:
- An immutable variable is like writing a value on a sticky note during the program’s work. Once written, you do not replace the sticky note with another value.
- A constant is like printing a value in the blueprint before the building is constructed. It is part of the design itself, not something decided while the building is being used.
So both are unchangeable, but they come from different stages:
letimmutable: decided while the program runsconst: decided when the program is compiled
Another simple way to remember it:
- immutable variable = fixed during execution
- constant = fixed before execution
Syntax and Examples
Immutable variable with let
fn main() {
let name = "Alice";
println!("{}", name);
// name = "Bob"; // error: cannot assign twice to immutable variable
}
This creates a normal local variable binding. It is immutable because mut was not used.
Mutable variable with mut
fn main() {
let mut count = 1;
count = 2;
println!("{}", count);
}
Constant with const
const MAX_RETRIES: u32 = 3;
fn main() {
println!("{}", MAX_RETRIES);
}
Notice that:
constusually uses uppercase names by convention- the type must be written explicitly
- the value must be a constant expression
Immutable variable can use runtime values
fn get_user_count() -> u32 {
42
}
fn main() {
let users = get_user_count();
println!("{}", users);
}
users is immutable, but it is not a constant. Its value comes from a function call at runtime.
const must be compile-time evaluable
const SECONDS_IN_MINUTE: u32 = 60;
const SECONDS_IN_HOUR: u32 = SECONDS_IN_MINUTE * 60;
fn main() {
println!("{}", SECONDS_IN_HOUR);
}
let inside functions vs const at wider scope
const APP_NAME: &str = "My App";
fn main() {
let version = 1;
println!("{} v{}", APP_NAME, version);
}
Here:
APP_NAMEis a compile-time constant shared across the programversionis a local immutable variable insidemain
Step by Step Execution
Consider this example:
const TAX_RATE: f64 = 0.1;
fn main() {
let price = 100.0;
let tax = price * TAX_RATE;
println!("Tax: {}", tax);
}
Step by step
-
The compiler sees
const TAX_RATE: f64 = 0.1;.- This value must be known at compile time.
TAX_RATEis available as a constant value.
-
The program starts running
main(). -
let price = 100.0;- A local immutable variable named
priceis created. - Its value is
100.0.
- A local immutable variable named
-
let tax = price * TAX_RATE;- The program reads
price. - The program reads the constant
TAX_RATE. - It calculates
100.0 * 0.1, which is10.0. - A new immutable variable
taxis created.
- The program reads
-
println!("Tax: {}", tax);- The output is printed.
What to notice
TAX_RATEexists as a compile-time constant.priceandtaxare regular immutable local bindings.- All three are not reassigned, but only one of them is a
const.
Now compare with this:
fn main() {
let price = 100.0;
let tax_rate = 0.1;
let tax = price * tax_rate;
println!("Tax: {}", tax);
}
This also works. The difference is that tax_rate is just a local immutable variable, not a compile-time constant item shared outside the function.
Real World Use Cases
When to use immutable let
Use immutable variables for ordinary values created during program execution:
- user input
- function results
- parsed data
- loop results
- temporary calculations
Example:
fn main() {
let input = String::from("42");
let number: i32 = input.parse().unwrap();
println!("{}", number);
}
number is immutable, but it depends on runtime work.
When to use const
Use const for fixed values that should be the same everywhere:
- API limits
- conversion factors
- default ports
- buffer sizes
- mathematical constants
- application-wide settings known at compile time
Example:
const DEFAULT_PORT: u16 = 8080;
const MAX_PACKET_SIZE: usize = 1024;
Common practical examples
Validation limits
const MIN_PASSWORD_LEN: usize = 8;
Array sizes
const BUFFER_SIZE: usize = 256;
let buffer = [0u8; BUFFER_SIZE];
Business rules
const FREE_SHIPPING_THRESHOLD: f64 = 50.0;
These are values that are better expressed as constants because they communicate intent clearly and are reusable.
Real Codebase Usage
In real Rust projects, developers usually follow a simple pattern:
- use
letfor local values inside functions - use
constfor shared fixed values
Common patterns
1. Configuration-like fixed values
const REQUEST_TIMEOUT_SECS: u64 = 30;
const MAX_LOGIN_ATTEMPTS: u8 = 5;
These values are easy to reuse and update in one place.
2. Validation rules
const USERNAME_MIN_LEN: usize = 3;
const USERNAME_MAX_LEN: usize = 20;
fn is_valid_username(name: &str) -> bool {
let len = name.len();
len >= USERNAME_MIN_LEN && len <= USERNAME_MAX_LEN
}
3. Guard clauses using constants
const MAX_ITEMS: usize = 100;
fn add_items(count: usize) -> Result<(), String> {
if count > MAX_ITEMS {
return Err("too many items".to_string());
}
Ok(())
}
4. Replacing magic numbers
Instead of this:
fn area(width: u32, height: u32) -> u32 {
if width > 1000 || height > 1000 {
return 0;
}
width * height
}
Developers often write this:
const MAX_DIMENSION: u32 = 1000;
fn area(width: u32, height: u32) -> u32 {
if width > MAX_DIMENSION || height > MAX_DIMENSION {
return 0;
}
width * height
}
This makes the code clearer and easier to maintain.
5. Local computation stays with let
fn total_with_tax(price: f64) -> f64 {
const TAX_RATE: f64 = 0.2;
let tax = price * TAX_RATE;
let total = price + tax;
total
}
Even inside a function, const can be useful for fixed values, while let handles computed intermediate values.
Common Mistakes
1. Thinking immutable let and const are identical
They are similar in one way: neither can be reassigned.
But they differ in:
- compile-time vs runtime evaluation
- required type annotation
- where they can be declared and used
2. Trying to create a const from a runtime value
Broken example:
fn get_port() -> u16 {
8080
}
const PORT: u16 = get_port();
This fails unless the expression is allowed in a constant context.
Use an immutable variable instead if the value is only known at runtime:
fn get_port() -> u16 {
8080
}
fn main() {
let port = get_port();
println!("{}", port);
}
3. Forgetting that const needs an explicit type
Broken example:
const LIMIT = 10;
Correct:
const LIMIT: i32 = 10;
4. Using let where a compile-time constant is clearer
Less clear:
fn main() {
let max_size = 1024;
println!("{}", max_size);
}
Better if the value is a fixed rule used in multiple places:
const MAX_SIZE: usize = 1024;
5. Confusing immutability with deep immutability
This is an important Rust idea: an immutable binding means you cannot reassign the binding itself. It does not automatically mean all internal state is impossible to change in every situation.
For beginner-level Rust, the key takeaway is:
let x = value;prevents reassignment ofxconst X: Type = value;defines a compile-time constant item
6. Using magic numbers instead of named constants
Harder to understand:
if retries > 3 {
println!("Stop");
}
Clearer:
const MAX_RETRIES: u32 = 3;
if retries > MAX_RETRIES {
println!("Stop");
}
Comparisons
| Feature | Immutable let | const |
|---|---|---|
| Reassignable? | No | No |
| Computed at runtime? | Yes, can be | No, must be compile-time evaluable |
| Requires explicit type? | No, often inferred | Yes |
| Typical scope | Usually local bindings | Local or item-level constant definitions |
| Good for temporary values? | Yes | Usually no |
| Good for shared fixed values? | Sometimes, but less ideal | Yes |
| Naming convention | snake_case | UPPER_SNAKE_CASE |
let vs const in code
fn main() {
let x = 5;
println!("{}", x);
}
x is immutable, local, and inferred.
const X: i32 = 5;
fn main() {
println!("{}", X);
}
X is a constant item, compile-time known, and explicitly typed.
const vs static
Rust also has static, which is related but different.
| Feature | const | static |
|---|---|---|
| Inlined value-like usage | Yes | Not primarily |
| Single fixed memory location | Not the main idea | Yes |
| Compile-time known | Yes | Yes |
| Common beginner use | Named fixed values | Global data |
If your question is only about immutable variables and const, the most important comparison is still:
- use
letfor normal bindings - use
constfor compile-time fixed values
Cheat Sheet
// Immutable variable
let x = 10;
// Mutable variable
let mut y = 20;
y = 30;
// Constant
const MAX_SIZE: usize = 1024;
Quick rules
letcreates a variable binding- variables are immutable by default
- add
mutto allow reassignment constcreates a constant itemconstmust have an explicit typeconstmust use a compile-time evaluable expression- use
UPPER_SNAKE_CASEfor constants by convention
Use let when
- the value is produced at runtime
- you need a local temporary value
- type inference is helpful
Use const when
- the value never changes
- the value is known at compile time
- you want a named fixed value reused across code
- you need a constant in places that require compile-time values
Easy memory trick
letimmutable = cannot reassign the variableconst= compile-time named value
Example
const LIMIT: i32 = 100;
fn main() {
let current = 42;
println!("limit: {}, current: {}", LIMIT, current);
}
FAQ
Is an immutable variable the same as a constant in Rust?
No. Both cannot be reassigned, but an immutable variable is a normal binding created with let, while a const is a compile-time constant with stricter rules.
Why does Rust need const if variables are immutable by default?
Because immutability alone only prevents reassignment. const also expresses that a value is fixed at compile time and can be used as a named constant throughout the program.
Does const require a type in Rust?
Yes. A constant must have an explicit type annotation.
Can a const be computed from a function call?
Only if the function and expression are allowed in a constant context. In beginner-level code, assume const values should be simple compile-time expressions unless you know otherwise.
When should I use let instead of const?
Use let for local values, function results, parsed input, and other runtime computations.
Can I declare const inside a function?
Yes. A constant can be declared inside a function if it represents a fixed compile-time value.
What naming style should constants use in Rust?
By convention, constants use UPPER_SNAKE_CASE, like MAX_BUFFER_SIZE.
Is static the same as const in Rust?
No. They are related but different. const is for compile-time constant values, while static refers to data stored in a fixed location for the program’s lifetime.
Mini Project
Description
Build a small Rust program that calculates order totals for an online store. This project demonstrates when to use const for fixed business rules and when to use immutable let bindings for values computed while the program runs.
Goal
Create a program that uses constants for store rules and immutable variables for runtime calculations, then prints a final order summary.
Requirements
[ "Define at least two constants for fixed store rules such as tax rate or free shipping threshold.", "Use immutable let bindings for the order subtotal and calculated values.", "Compute tax and final total using those values.", "Print a clear summary showing subtotal, tax, shipping, and total." ]
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.