Question

Immutable Variables vs const in Rust: What’s the Difference?

rustconstantsimmutability

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:

  • x cannot be reassigned
  • x is 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:

  • let immutable: decided while the program runs
  • const: decided when the program is compiled

Another simple way to remember it:

  • immutable variable = fixed during execution
  • constant = fixed before execution
Take Quiz

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:

  • const usually 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_NAME is a compile-time constant shared across the program
  • version is a local immutable variable inside main

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

  1. The compiler sees const TAX_RATE: f64 = 0.1;.

    • This value must be known at compile time.
    • TAX_RATE is available as a constant value.
  2. The program starts running main().

  3. let price = 100.0;

    • A local immutable variable named price is created.
    • Its value is 100.0.
  4. let tax = price * TAX_RATE;

    • The program reads price.
    • The program reads the constant TAX_RATE.
    • It calculates 100.0 * 0.1, which is 10.0.
    • A new immutable variable tax is created.
  5. println!("Tax: {}", tax);

    • The output is printed.

What to notice

  • TAX_RATE exists as a compile-time constant.
  • price and tax are 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.

Take Quiz

Real Codebase Usage

In real Rust projects, developers usually follow a simple pattern:

  • use let for local values inside functions
  • use const for 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 of x
  • const 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

FeatureImmutable letconst
Reassignable?NoNo
Computed at runtime?Yes, can beNo, must be compile-time evaluable
Requires explicit type?No, often inferredYes
Typical scopeUsually local bindingsLocal or item-level constant definitions
Good for temporary values?YesUsually no
Good for shared fixed values?Sometimes, but less idealYes
Naming conventionsnake_caseUPPER_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.

Featureconststatic
Inlined value-like usageYesNot primarily
Single fixed memory locationNot the main ideaYes
Compile-time knownYesYes
Common beginner useNamed fixed valuesGlobal data

If your question is only about immutable variables and const, the most important comparison is still:

  • use let for normal bindings
  • use const for compile-time fixed values
Take Quiz

Cheat Sheet

// Immutable variable
let x = 10;

// Mutable variable
let mut y = 20;
y = 30;

// Constant
const MAX_SIZE: usize = 1024;

Quick rules

  • let creates a variable binding
  • variables are immutable by default
  • add mut to allow reassignment
  • const creates a constant item
  • const must have an explicit type
  • const must use a compile-time evaluable expression
  • use UPPER_SNAKE_CASE for 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

  • let immutable = cannot reassign the variable
  • const = 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.

Take Quiz

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." ]

Take Quiz

Keep learning

Related questions

View more