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