Article by Ayman Alheraki on January 11 2026 10:37 AM
Understanding how constants, mutability, and shadowing operate in C++ and Rust is fundamental for writing clear, efficient, and safe programs. Both languages provide mechanisms to control variable mutability and lifetime, but they differ significantly in their design philosophies and enforcement.
Definition: In C++, constants are variables whose value cannot be modified after initialization. The keyword const is used to declare constants.
const int MAX_SIZE = 100;Characteristics:
const variables must be initialized at the time of declaration.
The compiler enforces that attempts to modify a const variable result in a compilation error.
constexpr (introduced in C++11 and expanded in later standards) denotes values or functions that can be evaluated at compile time, allowing for better optimization (ISO C++20 Standard, cppreference on constexpr).
constexpr variables are implicitly const, but with the added guarantee of compile-time evaluation.
Example:
constexpr double PI = 3.14159;References: https://en.cppreference.com/w/cpp/language/const https://en.cppreference.com/w/cpp/language/constexpr
Definition: Rust provides two main ways to define immutable data:
const defines a constant value that is always immutable and must be known at compile time.
let bindings are immutable by default unless declared with mut.
Characteristics:
const values in Rust must be explicitly typed and are evaluated at compile time. They are globally available and cannot be changed at runtime.
let variables are immutable unless prefixed with mut. This design enforces safety and intentional mutability (Rust Reference, Rust Book).
Examples:
const MAX_SIZE: u32 = 100;let x = 5; // immutablelet mut y = 10; // mutableReferences: https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html https://doc.rust-lang.org/reference/items/constant-items.html
By default, variables in C++ are mutable unless declared with const.
Mutable variables can be reassigned and modified freely.
C++ does not enforce immutability beyond the const qualifier, and const_cast can override constness, which can lead to undefined behavior if misused (cppreference).
Example:
int x = 10; // mutablex = 20; // allowedconst int y = 30;// y = 40; // compilation errorReferences: https://en.cppreference.com/w/cpp/language/cv
In Rust, immutability is the default for all variables, and explicit mut is required to declare a mutable variable. This design choice is fundamental to Rust’s safety guarantees, reducing unintended side-effects (Rust Book).
Mutability applies to the binding itself, not the data. To mutate data behind a reference, Rust uses special types like Cell and RefCell (Rust official docs).
Example:
let x = 5; // immutable by defaultlet mut y = 10; // mutable variabley = 15; // allowedReferences: https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html https://doc.rust-lang.org/std/cell/
Shadowing refers to declaring a new variable with the same name as a previous variable in the same or inner scope, effectively "hiding" the earlier variable.
C++ does not support variable shadowing within the same scope. Shadowing may occur in nested scopes (e.g., inside blocks or functions), but it is generally discouraged due to potential confusion and errors (ISO C++ Standard).
Shadowing in C++ often involves hiding class member variables or global variables using local variable names, but the practice can lead to bugs and is often avoided with explicit naming conventions.
Example:
int x = 5;{ int x = 10; // shadows outer x within this block std::cout << x; // prints 10}std::cout << x; // prints 5References: https://en.cppreference.com/w/cpp/language/scope
Rust allows and encourages shadowing by re-declaring variables with the same name using let. This enables changing the type or mutability of a variable in a new scope without needing to create a new name, improving code clarity and safety (Rust Book).
Shadowing allows immutable bindings to be “re-bound” as mutable or vice versa.
Example:
let x = 5;let x = x + 1; // shadows previous x, x is now 6let x = "hello"; // shadows again with a different typeReferences: https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing
| Feature | C++ | Rust |
|---|---|---|
| Constants | const and constexpr for compile-time constants | const for compile-time constants; let is immutable by default |
| Mutability | Mutable by default; const for immutability; const_cast allows override (unsafe) | Immutable by default; explicit mut required; no unsafe mutability override |
| Shadowing | Limited to nested scopes; discouraged | Encouraged; allows rebinding and type changes |
| Compile-time evaluation | Via constexpr from C++11 onwards | Via const and const fn (const functions) |
ISO C++ Standard and cppreference on const and constexpr
https://isocpp.org/std/the-standard
https://en.cppreference.com/w/cpp/language/const
https://en.cppreference.com/w/cpp/language/constexpr
Rust Book: Variables and Mutability https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html
Rust Reference: Constants https://doc.rust-lang.org/reference/items/constant-items.html
C++ cv-qualifiers (const/volatile) https://en.cppreference.com/w/cpp/language/cv
Rust std::cell for interior mutability https://doc.rust-lang.org/std/cell/
C++ Scope and Variable Shadowing https://en.cppreference.com/w/cpp/language/scope