Constants & Statics
Reviewed & published by Brayan K
Constants and statics are Rust's two ways to declare fixed, named values — a const is a compile-time value inlined at every use, while a static is a single value with a fixed memory location that lives for the whole program.
Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll learn const vs static, naming conventions, const fn and compile-time evaluation, the 'static lifetime, and how shadowing differs from a constant.
What You'll Learn in This Lesson
- Declaring const with a required type annotation
- SCREAMING_SNAKE_CASE naming for both const and static
- How const inlines vs how static has one fixed address
- const fn and evaluating values at compile time
- The 'static lifetime that statics and literals carry
- Shadowing vs const vs mut — which to reach for
1️⃣ const: Compile-Time Constants
A const declares a value fixed at compile time. You must annotate its type, the value must be a constant expression, and the convention is SCREAMING_SNAKE_CASE. The compiler inlines the value wherever you use it, so it behaves like a named literal — and you can declare one at module scope or inside a function.
// const: a compile-time constant. Convention: SCREAMING_SNAKE_CASE.
// You MUST annotate the type, and it's inlined wherever it's used.
const MAX_SCORE: u32 = 100;
const PI: f64 = 3.14159;
fn main() {
println!("MAX_SCORE = {}", MAX_SCORE);
// const can be declared in any scope, including inside a function.
const GREETING: &str = "hello";
println!("{}, world", GREETING);
// Using a const in an expression — it behaves like a literal.
let area = PI * 2.0 * 2.0;
println!("area = {:.2}", area);
}Constants are perfect for magic numbers, configuration limits, and mathematical values. Naming them gives the number meaning and a single place to change it.
2️⃣ static & const fn
A static also names a fixed value, but unlike const it has a single memory address and the 'static lifetime — it exists for the entire program. A const fn is a function the compiler can run at compile time, so its result can initialize a const.
// A `static` is a single value with a fixed memory location and 'static lifetime.
static APP_NAME: &str = "RustApp";
static VERSION: u32 = 2;
// A `const fn` can run at COMPILE TIME, so its result can initialize a const.
const fn square(n: u32) -> u32 {
n * n
}
// This const is computed by the const fn during compilation.
const NINE: u32 = square(3);
fn main() {
println!("{} v{}", APP_NAME, VERSION);
println!("square(3) at compile time = {}", NINE);
// A const fn can also be called normally at runtime.
let x = square(5);
println!("square(5) at runtime = {}", x);
}3️⃣ Shadowing vs const
Shadowing reuses a variable name with a fresh let, creating a brand-new binding that can even change the type. That's quite different from a const, which is one fixed value with one type. Don't confuse shadowing with mut either — shadowing makes a new binding, while mut changes the existing one.
const LIMIT: i32 = 10;
fn main() {
// SHADOWING reuses a name with `let`; each is a NEW, separate binding.
let x = 5;
let x = x + 1; // shadows the first x
let x = x * 2; // shadows again
println!("shadowed x = {}", x);
// Shadowing can even change the TYPE — const cannot.
let value = "42"; // &str
let value: i32 = value.parse().unwrap(); // now i32
println!("value + LIMIT = {}", value + LIMIT);
// A const is fixed forever; it has one type and one value.
println!("LIMIT stays {}", LIMIT);
}Your turn. Fill in the blanks marked ___, then run it.
// 🎯 YOUR TURN — fill in the blanks, then run it.
// 1) Declare a compile-time constant MAX of type u32 set to 50.
___ MAX: u32 = 50;
// 2) Make triple usable at compile time by marking it a const fn.
___ fn triple(n: u32) -> u32 {
n * 3
}
// 3) Initialize this const by calling the const fn.
const RESULT: u32 = triple(4);
fn main() {
println!("MAX = {}", MAX);
println!("triple(4) = {}", RESULT);
}
// ✅ Expected output:
// MAX = 50
// triple(4) = 12Mini-Challenge: Compile-Time Time Math
Compose constants and a const fn so the compiler does the arithmetic before your program even runs. Run it with cargo run and check the output.
// 🎯 MINI-CHALLENGE: compile-time time math.
//
// 1. Declare two consts: SECONDS_PER_MINUTE = 60 and MINUTES_PER_HOUR = 60.
// 2. Write a const fn seconds_in_hours(hours: u32) -> u32 that multiplies
// hours by both constants.
// 3. Use it to initialize a const DAY_SECONDS = seconds_in_hours(24).
// 4. Print seconds in 1 hour, 8 hours, and the DAY_SECONDS const.
//
// ✅ Example output:
// 1 hour = 3600 seconds
// 8 hours = 28800 seconds
// 1 day = 86400 seconds
fn main() {
// call your const fn here
}Common Errors (and the fix)
- ❌ "missing type for `const` item" — A const can't infer its type. ✅ Always annotate it, e.g. const MAX: u32 = 100;.
- ❌ "calls in constants are limited..." — You called a regular function to init a const. ✅ Make the function a const fn.
- ❌ "use of mutable static is unsafe" — You touched a static mut. ✅ Use a Mutex or atomic for shared mutable state instead.
- ❌ Trying to reassign a const — Constants never change. ✅ If you need to vary a value, use a let mut variable.
Pro Tips
- 💡 Reach for const by default — you rarely need a static unless you need one fixed address.
- 💡 Name your magic numbers: a well-named const documents intent and centralizes changes.
- 💡 Push work to compile time: a const fn can precompute lookup values so there's zero runtime cost.
📋 Quick Reference — Constants & Statics
| Form | Key trait |
|---|---|
| const X: T = .. | Inlined; no fixed address |
| static X: T = .. | One address; 'static lifetime |
| const fn f() .. | Evaluable at compile time |
| let x = ..; let x = .. | Shadow: new binding, new type ok |
| let mut x = .. | Same binding, mutate in place |
🎉 Lesson Complete!
- ✅ const needs a type, is inlined, and uses SCREAMING_SNAKE_CASE
- ✅ static has one fixed address and the 'static lifetime
- ✅ A const fn runs at compile time to initialize constants
- ✅ Shadowing makes a new binding (new type ok); mut mutates in place; const never changes
Practice quiz
What must you always provide when declaring a const?
- The mut keyword
- A lifetime
- An explicit type annotation
- A trait bound
Answer: An explicit type annotation. A const cannot infer its type, so you must annotate it, e.g. const MAX: u32 = 100;.
What is the conventional naming style for const and static items?
- SCREAMING_SNAKE_CASE
- camelCase
- snake_case
- PascalCase
Answer: SCREAMING_SNAKE_CASE. Constants and statics conventionally use SCREAMING_SNAKE_CASE.
How does a const differ from a static in memory?
- A const has a fixed address; a static is inlined
- Both have one address
- Both are inlined identically
- A const is inlined at each use; a static has one fixed memory address
Answer: A const is inlined at each use; a static has one fixed memory address. A const is inlined wherever used; a static lives at a single fixed address for the whole program.
What does the 'static lifetime mean?
- The value is mutable
- The value lives for the entire duration of the program
- The value is on the stack
- The value is dropped immediately
Answer: The value lives for the entire duration of the program. 'static means the data lives for the whole program, so references to it are valid forever.
What is a const fn?
- A function the compiler can evaluate at compile time
- A function that cannot be called at runtime
- A constant that acts like a function
- A function returning only constants
Answer: A function the compiler can evaluate at compile time. A const fn can run at compile time (and still at runtime), so it can initialize a const.
What does this print? const fn square(n: u32) -> u32 { n * n } const NINE: u32 = square(3); fn main() { println!("{}", NINE); }
- 3
- 6
- 9
- It does not compile
Answer: 9. square(3) is evaluated at compile time to 9 and stored in NINE.
What does this print? const fn seconds_in_hours(h: u32) -> u32 { h * 60 * 60 } const DAY: u32 = seconds_in_hours(24); fn main() { println!("{}", DAY); }
- 1440
- 86400
- 3600
- 24
Answer: 86400. 24 * 60 * 60 = 86400 seconds in a day, computed at compile time.
What is shadowing?
- Mutating a variable in place
- Hiding a const
- Declaring a static
- Reusing a name with let to create a brand-new binding, possibly a new type
Answer: Reusing a name with let to create a brand-new binding, possibly a new type. Shadowing makes a new binding with let that hides the old one and can change type.
What does this print? let value = "42"; let value: i32 = value.parse().unwrap(); println!("{}", value + 10);
- 4210
- 52
- 42
- It does not compile
Answer: 52. Shadowing turns the &str into i32 42, and 42 + 10 is 52.
Why is a static mut considered unsafe to access?
- It is too slow
- It cannot be typed
- Shared mutable global state is not thread-safe
- It has no address
Answer: Shared mutable global state is not thread-safe. Accessing static mut requires unsafe because shared mutable globals invite data races; prefer Mutex or atomics.
Continue this course
- Previous: Type Conversions (From, Into, as, TryFrom)
- Next: Comparison & Ordering (PartialEq, Ord) — Derive equality and ordering, sort with keys, and why floats are PartialOrd
- Quick reference: Rust cheat sheet
Frequently asked questions
What's the difference between const and static?
Both name a fixed value, but they work differently. A const is a compile-time constant with no fixed address — the compiler inlines its value at every use site, almost like a named literal, and you can declare one in any scope. A static has a single, fixed memory location that exists for the entire run of the program, so every reference to it points to the same address. For ordinary named values you almost always want const. Reach for static only when you specifically need one shared address, for example to take a 'static reference to it or for interop with C.
What does it mean that a static has a 'static lifetime?
The 'static lifetime means a value lives for the entire duration of the program. Because a static item is stored at a fixed location that never goes away, a reference to it is valid forever, which is exactly what the 'static lifetime expresses. String literals like "hello" are also 'static — they're baked into the binary. This is why a function can return a &'static str pointing at a literal: the data outlives every call. Lifetimes are covered in depth in a later lesson, but for statics the rule is simple: they last as long as the program does.
What is a const fn and when can I use one?
A const fn is a function the compiler is allowed to evaluate at compile time, so its result can initialize a const or a static or be used anywhere a constant expression is required, like an array length. The same function still works at runtime too. The trade-off is that a const fn may only use operations Rust can guarantee are deterministic at compile time, so some standard-library calls and features aren't available inside one. Use const fn for pure, deterministic computations you want resolved before the program ever runs, such as deriving one constant from others.
How is shadowing different from a const or a mutable variable?
Shadowing is when you declare a new variable with let that reuses an existing name — it creates a brand-new binding that hides the old one, and it can even have a different type. That's different from mut, which reuses the same binding and only allows new values of the same type. A const is different again: it's a single fixed value, must be a constant expression, requires a type annotation, and conventionally uses SCREAMING_SNAKE_CASE. Use shadowing to transform a value step by step, mut for genuine in-place mutation, and const for values that never change.