Skip to content

Instantly share code, notes, and snippets.

@nihalpasham
Created April 3, 2026 05:01
Show Gist options
  • Select an option

  • Save nihalpasham/6b23d6bc12a7fb90f101a299b72c3d79 to your computer and use it in GitHub Desktop.

Select an option

Save nihalpasham/6b23d6bc12a7fb90f101a299b72c3d79 to your computer and use it in GitHub Desktop.
Promoted Constants in Rust MIR

Promoted Constants in Rust MIR

This document explains how Rust handles promoted constants, why they exist.

Table of Contents

  1. What Are Promoted Constants?
  2. MIR Syntax
  3. Memory Layout
  4. Code References

What Are Promoted Constants?

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.

Benefits of Promotion

  1. Zero runtime cost - No stack allocation or initialization at runtime
  2. Memory efficiency - Single copy shared across all calls
  3. Enables optimizations - Compiler can inline/constant-fold

Promoted Constants in Rust MIR

A minimal crate demonstrating how Rust promotes compile-time constant values out of function bodies and into static memory.

The Code

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.

Dumping the MIR

cargo +nightly rustc -- -Z unpretty=mir

The 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.

MIR Syntax

The promoted[N] Syntax

const <function_path>::promoted[N]

Where:

  • <function_path> — Full path to the containing function
  • promoted — Special keyword indicating a promoted constant
  • [N] — Index (a function can have multiple promoted constants)

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]
}

Memory Layout

This is the part that isn't obvious from the MIR text alone.

What the Compiler Actually Stores

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))]
    }
}

Why Are The Bytes Zero?

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)"

Where Is The Actual Data?

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.

Visual Diagram

┌───────────────────────────────────────────────────────────────────┐
│  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──┘        │                            │
│  └───────────────────────────────────┘                            │
└───────────────────────────────────────────────────────────────────┘

Key Takeaways

  1. Promoted constants are referencespromoted[0] is &'static T, not T
  2. The allocation contains a pointer — Bytes are zeros, with provenance pointing elsewhere
  3. Follow provenance for the real data — The struct bytes live in a separate AllocId
  4. Each promoted constant gets its own MIR body — A self-contained mini-function that constructs the value

Reproducing

cargo +nightly rustc -- -Z unpretty=mir

Related Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment