BasicsImpCodeStory_Variables.html
copyright © James Fawcett
Revised: 05/11/2026
2.0 Prologue
A variable is a named binding from an identifier to a storage location. How languages
handle declaration syntax, mutability defaults, scope boundaries, and compile-time
constants shapes both program correctness and readability.
2.1 Variable Declaration
Most modern languages support type inference: the compiler deduces the
type from the initializer, removing redundant annotations while retaining full static
typing.
// Rust (let introduces a binding; type inferred)
let x = 42; // i32 inferred
let y: f64 = 3.14; // explicit annotation
// C++ (auto deduces type from initializer)
auto x = 42; // int
auto y = 3.14; // double
// C# (var deduces type; requires an initializer)
var x = 42; // int
var y = 3.14; // double
// Python (no declaration keyword; assignment creates the binding)
x = 42
y = 3.14
Python bindings are not typed - the name can be rebound to a value of any type.
Rust, C++, and C# bindings are statically typed; the type is fixed at declaration.
2.2 Mutability
Languages differ in their default mutability. Rust bindings are
immutable by default; C++, C#, and Python bindings are mutable by default.
// Rust (immutable by default; opt-in mutation)
let x = 5;
// x = 6; // compile error
let mut y = 5;
y = 6; // ok
// C++ (mutable by default; const opts out)
int y = 5;
y = 6; // ok
const int x = 5;
// x = 6; // compile error
// C#
int y = 5;
y = 6; // ok
const int x = 5; // compile-time constant, not a variable
// Python (no enforced immutability for scalars)
x = 5
x = 6 # rebinds the name; previous int object is unchanged
Immutability by default pushes mutation to be explicit, making data flow easier to
reason about. Rust’s borrow checker enforces these rules at compile time.
2.3 Scope and Lifetime
Scope is the region of source text where a name is visible.
Lifetime is the duration for which the binding’s storage is valid.
In languages with automatic storage, scope and lifetime coincide: the binding lives as
long as its enclosing block.
// Rust (block scope; value dropped at closing brace)
{
let s = String::from("hello");
// s is valid here
}
// s is dropped here; memory freed
// C++ (same block-scope rule; destructor called at closing brace)
{
std::string s = "hello";
} // destructor runs here
// C# / Python: garbage-collected; lifetimes are not block-scoped
Rust goes further: it tracks lifetimes through references and rejects code that
would allow a reference to outlive the value it points to, eliminating use-after-free
at compile time.
2.4 Shadowing
Shadowing occurs when a new binding with the same name is introduced
in the same or an inner scope, hiding the previous binding. Unlike mutation, shadowing
can change the type of the name.
// Rust (shadowing is explicit and intentional)
let x = 5;
let x = x + 1; // shadows previous x; new binding of type i32
let x = x.to_string(); // shadows again; now x is String
// C++ (inner scope shadows outer)
int x = 5;
{
double x = 2.5; // hides outer x within this block
}
// outer int x visible again
// C# and Python behave similarly within nested scopes
Rust’s same-scope shadowing is used to transform a value without a separate name,
keeping code compact after parsing or converting input.
2.5 Constants and Statics
Constants are evaluated at compile time and inlined wherever they appear.
Statics have a single address that persists for the duration of the
program (static storage duration).
// Rust
const MAX_SIZE: usize = 1024; // compile-time constant
static GREETING: &str = "hello"; // static storage, 'static lifetime
static mut COUNTER: u32 = 0; // mutable static (unsafe to access)
// C++
constexpr int MAX_SIZE = 1024; // compile-time constant
static const char* GREETING = "hello"; // static storage
// C#
const int MaxSize = 1024; // compile-time constant
static readonly string Greeting = "hello"; // runtime-initialized static
// Python (convention only; no enforced constants)
MAX_SIZE = 1024 # uppercase names signal "don't mutate this"
Mutable statics in Rust require unsafe because concurrent access without
synchronization is undefined behavior. Prefer atomic types or mutexes for shared
mutable state.
2.6 Epilogue
Binding rules determine which operations are safe before execution begins. Immutability
by default and strong scoping eliminate a large class of bugs at zero runtime cost.
The next chapter examines the operators that act on bound values.
2.7 References
Rust Variables and Mutability - The Book
C++ Storage Duration - cppreference
C# const - Microsoft
Python Execution Model - Binding of Names