Variables and Mutability

Compile-Time Evaluation

Compile-time evaluation (often called const evaluation or CTFE) is the execution of Rust expressions and functions by rustc during compilation rather than at runtime when the compiled binary runs. Values computed at compile time are baked directly into the binary executable, eliminating runtime CPU computation and allowing complex constants, lookup tables, and array lengths to be verified before the program ever starts.

Contexts That Require Compile-Time Evaluation

Certain places in Rust grammar must be evaluated at compile time—called const contexts. Any expression placed in a const context is evaluated by rustc's internal MIR interpreter (Miri engine) during compilation:

Const Context Example
const item initializer const BUFFER_SIZE: usize = 4 * 1024;
static item initializer static START_COUNT: u32 = 10 + 5;
Array type length [T; N] let buf: [u8; 64 * 2] = [0; 128];
Array repeat count [val; N] let zeros = [0_i32; 16 * 4];
Enum explicit discriminant enum Code { Ok = 200, NotFound = 400 + 4 }
Inline const { ... } block let table = const { build_table() };
const KILOBYTE: usize = 1024;
const PAGE_SIZE: usize = 4 * KILOBYTE;

fn main() {
    // Array length `PAGE_SIZE / 512` (`8`) is evaluated at compile time
    let sectors: [u64; PAGE_SIZE / 512] = [0; 8];

    println!("Page size: {PAGE_SIZE} bytes, sectors count: {}", sectors.len());
}
Page size: 4096 bytes, sectors count: 8

Compile-Time Functions (const fn)

A regular fn cannot be called inside a const initializer or array length. Marking a function with const fn tells the compiler that the function is safe to execute both at compile time (when called from a const context) and at runtime (when called with runtime variables):

const fn gcd(a: u64, b: u64) -> u64
          |
          +---> Called in `const X: u64 = gcd(48, 18);`  --> Evaluated at COMPILE TIME (embedded as `6`)
          |
          +---> Called in `let y = gcd(user_input, 18);` --> Executed at RUNTIME like a normal `fn`
const fn factorial(n: u64) -> u64 {
    let mut result = 1;
    let mut i = 2;

    // `while` loops, `if`/`else`, `match`, and local `mut` variables are all valid inside `const fn`!
    while i <= n {
        result *= i;
        i += 1;
    }

    result
}

// Computed at compile time:
const FACT_10: u64 = factorial(10);

fn main() {
    // Computed at runtime:
    let runtime_input = 6;
    let fact_6 = factorial(runtime_input);

    println!("10! (compile-time constant) = {FACT_10}");
    println!("6!  (runtime computation)   = {fact_6}");
}
10! (compile-time constant) = 3628800
6!  (runtime computation)   = 720

Building Lookup Tables at Compile Time

A const fn can initialize a whole array at compile time so the program starts with a precomputed lookup table in read-only memory:

const fn generate_squares<const N: usize>() -> [u32; N] {
    let mut table = [0; N];
    let mut i = 0;
    while i < N {
        table[i] = (i as u32) * (i as u32);
        i += 1;
    }
    table
}

const SQUARES: [u32; 8] = generate_squares();

fn main() {
    println!("Precomputed squares: {SQUARES:?}");
}
Precomputed squares: [0, 1, 4, 9, 16, 25, 36, 49]

Inline const { ... } Blocks

Normally, an expression inside a regular function (let x = factorial(10);) is evaluated at runtime unless assigned to a named const item first. An inline const block (const { ... }) forces an expression inside a function body to be evaluated at compile time without needing to declare a separate named const:

const fn compute_mask(bits: u32) -> u64 {
    (1_u64 << bits) - 1
}

fn main() {
    // Guaranteed to be evaluated at compile time and embedded as a constant literal:
    let lower_16_mask = const { compute_mask(16) };

    println!("Mask: 0x{lower_16_mask:04X}");
}
Mask: 0xFFFF

What Is Allowed and Forbidden at Compile Time

Because compile-time evaluation must be 100% deterministic, reproducible across build machines, and free of host OS side effects, const contexts and const fn allow pure computation while forbidding OS and heap operations:

Operation Allowed in const / const fn? Reason
Arithmetic, bitwise, and logical operators Yes Purely deterministic computation
Local let and let mut variables Yes Mutating local variables during const evaluation is completely safe
if, else, match, loop, while Yes Control flow is evaluated directly by rustc's const interpreter
Calling other const fn functions Yes Composable compile-time functions
panic!() and assert!() Yes Turns into a compile-time error if triggered during const evaluation!
File I/O, networking, environment variables, or printing (println!) No Depends on runtime OS environment and has side effects
Heap allocations (String::new, vec![1, 2, 3], Box::new) No Heap pointers created at compile time cannot be transferred directly to the runtime allocator
Calling a non-const function No (E0015) Only functions marked const fn guarantee compile-time determinism

Compile-Time Panics and Overflow Detection

If a const expression or const fn triggers a panic!, assert!, division by zero, out-of-bounds array index, or integer overflow during compile-time evaluation, compilation halts immediately with E0080:

// ❌ does not compile
const fn checked_port(port: u16) -> u16 {
    assert!(port >= 1024, "port must be unprivileged (>= 1024)");
    port
}

const BAD_PORT: u16 = checked_port(80);

fn main() {
    println!("{BAD_PORT}");
}
error[E0080]: evaluation of constant value failed
 --> src/main.rs:6:23
  |
2 |     assert!(port >= 1024, "port must be unprivileged (>= 1024)");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'port must be unprivileged (>= 1024)', src/main.rs:2:5
...
6 | const BAD_PORT: u16 = checked_port(80);
  |                       ^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

Calling a Non-const Function in a const Context (E0015)

// ❌ does not compile
fn regular_double(x: i32) -> i32 {
    x * 2
}

const VALUE: i32 = regular_double(10);

fn main() {}
error[E0015]: cannot call non-const fn `regular_double` in constants
 --> src/main.rs:5:20
  |
5 | const VALUE: i32 = regular_double(10);
  |                    ^^^^^^^^^^^^^^^^^^
  |
  = note: calls in constants are limited to constant functions, tuple structs and tuple variants

error: aborting due to 1 previous error