This document explains how Rust handles promoted constants, why they exist.
When you write Rust code like:
let is_in_range = (8..16).contains(&lane_id);Rust needs to create a Range<u32> struct {start: 8, end: 16} somewhere. There are two options:
| Option | Location | Lifetime | When Created |
|---|---|---|---|
| Stack allocation | Stack frame | Function scope | Runtime |
| Promotion | Static memory | 'static |
Compile time |
Rust chooses promotion when the value is:
- Compile-time constant
- Immutable
- Has no side effects
The term "promoted" means the value is promoted from a temporary stack allocation to a permanent static allocation.
- Zero runtime cost - No stack allocation or initialization at runtime
- Memory efficiency - Single copy shared across all calls
- Enables optimizations - Compiler can inline/constant-fold
A minimal crate demonstrating how Rust promotes compile-time constant values out of function bodies and into static memory.
fn check_range(lane_id: u32) -> bool {
let is_in_range = (8..16).contains(&lane_id);
is_in_range
}When you write (8..16), Rust creates a Range<u32> struct {start: 8, end: 16}. Rather than allocating it on the stack at runtime, the compiler promotes it to static memory — a single copy, created at compile time, shared across every call.
cargo +nightly rustc -- -Z unpretty=mirThe output for check_range is:
fn check_range(_1: u32) -> bool {
debug lane_id => _1;
let mut _0: bool;
let mut _2: &std::ops::Range<u32>;
let _3: &u32;
bb0: {
_2 = const check_range::promoted[0];
_3 = &_1;
_0 = std::ops::Range::<u32>::contains::<u32>(move _2, copy _3) -> [return: bb1, unwind continue];
}
bb1: {
return;
}
}
Notice that (8..16) is nowhere in the function body. Instead, _2 is assigned from const check_range::promoted[0] — a reference to a promoted constant that lives in its own separate MIR body:
const check_range::promoted[0]: &std::ops::Range<u32> = {
let mut _0: &std::ops::Range<u32>;
let mut _1: std::ops::Range<u32>;
bb0: {
_1 = std::ops::Range::<u32> { start: const 8_u32, end: const 16_u32 };
_0 = &_1;
return;
}
}
The range {start: 8, end: 16} is constructed once at compile time, and check_range receives a &'static Range<u32> pointing to it.
const <function_path>::promoted[N]
Where:
<function_path>— Full path to the containing functionpromoted— Special keyword indicating a promoted constant[N]— Index (a function can have multiple promoted constants)
If a function has multiple promoted constants, each gets a sequential index:
fn example(x: u32, y: u32) {
let a = (8..16).contains(&x); // promoted[0] = &Range{8, 16}
let b = (20..30).contains(&y); // promoted[1] = &Range{20, 30}
let c = &[1, 2, 3]; // promoted[2] = &[i32; 3]
}This is the part that isn't obvious from the MIR text alone.
If you inspect promoted[0] through the compiler's internal APIs, you get:
Constant {
type: &Range<u32> // A POINTER type, not Range directly!
allocation: {
// The pointer allocation (8 bytes on 64-bit)
bytes: [0, 0, 0, 0, 0, 0, 0, 0] // All zeros!
provenance: [(offset=0, AllocId(6))]
}
}
The bytes field contains the pointer value, not the struct data. The zeros are a placeholder — the actual address is determined at link time.
The provenance field tells us: "This pointer points to AllocId(6)"
The struct data lives in a separate allocation referenced by provenance:
AllocId(6) = Allocation {
bytes: [8, 0, 0, 0, 16, 0, 0, 0] // start=8, end=16 (little-endian u32)
provenance: [] // No nested pointers
}
So the promoted constant is really two allocations: a pointer (all zeros + provenance) that refers to the actual struct bytes elsewhere. A compiler backend needs to follow the provenance to reach the real data.
┌───────────────────────────────────────────────────────────────────┐
│ promoted[0]: &Range<u32> │
│ │
│ ┌───────────────────────────────────┐ │
│ │ Allocation (the pointer itself) │ │
│ │ bytes: [0,0,0,0,0,0,0,0] │────── provenance ─────┐ │
│ │ provenance: AllocId(6) │ │ │
│ └───────────────────────────────────┘ │ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────┐ │
│ │ AllocId(6) - The actual Range │ │
│ │ bytes: [8,0,0,0, 16,0,0,0] │ ◄── This is {8, 16}! │
│ │ └─start─┘ └──end──┘ │ │
│ └───────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
- Promoted constants are references —
promoted[0]is&'static T, notT - The allocation contains a pointer — Bytes are zeros, with provenance pointing elsewhere
- Follow provenance for the real data — The struct bytes live in a separate
AllocId - Each promoted constant gets its own MIR body — A self-contained mini-function that constructs the value
cargo +nightly rustc -- -Z unpretty=mir