How to fix Rust E0015 calls in constants are limited

Fix Rust E0015 by marking the called function with the `const` keyword to allow compile-time evaluation.

When the compiler refuses to call your function

You are building a configuration system for a Rust application. You want a constant that holds a computed default value, so you write const DEFAULT_TIMEOUT: u64 = calculate_timeout();. The compiler rejects this with E0015: "calls in constants are limited." You stare at the screen. The function is just arithmetic. It takes a number and returns a number. Why can't Rust call it?

The error is not a bug. It is a boundary. Rust draws a hard line between code that runs while the compiler is building your binary and code that runs when your program starts. E0015 appears when you try to cross that line without permission.

The blueprint rule

Think of const as the blueprint phase of your program. When you define a constant, you are telling the compiler to bake the value directly into the binary before the program ever runs. The compiler acts like an architect drawing a blueprint. It can do simple arithmetic, look up array indices, and call functions that are explicitly marked safe for blueprint work. It cannot call functions that require the building to exist, like measuring a room or fetching data from a server.

E0015 happens when you ask the architect to do construction work while they are still holding the pencil. The function you called is a construction worker. It expects a runtime environment with memory allocation, system calls, and dynamic state. The compiler does not have that environment. It only has a restricted evaluation engine designed for compile-time calculations.

Rust enforces this separation to guarantee zero-cost abstractions. Constants are inlined everywhere they are used. If the compiler cannot compute the value now, it cannot inline it later. The language would have to insert hidden runtime initialization code, which breaks the promise that constants are free and available instantly.

Fixing E0015 with const fn

The direct fix is to mark your function as const. This tells the compiler that the function body contains only operations safe for the blueprint phase. The compiler checks the function against a strict list of allowed operations. If the function passes, it becomes available in constant contexts.

// This function runs at runtime. The compiler cannot call it inside a const.
fn calculate_timeout() -> u64 {
    30 * 1000
}

// E0015: calls in constants are limited.
// The compiler rejects this because calculate_timeout is not const.
const DEFAULT_TIMEOUT: u64 = calculate_timeout();

Adding the const keyword resolves the error. The compiler now knows it can evaluate the function during compilation.

// Marking this const promises the compiler the body is safe for compile-time evaluation.
const fn calculate_timeout() -> u64 {
    30 * 1000
}

// The compiler inlines the result directly into the binary.
// No function call exists at runtime.
const DEFAULT_TIMEOUT: u64 = calculate_timeout();

The const keyword on a function is a contract. You are promising that the function does not use heap allocation, I/O, trait objects, or any feature that requires a running program. The compiler verifies this contract. If you break it, you get a different error pointing to the specific line inside the function that violates the rules.

What const actually does to your code

Understanding const requires knowing how Rust handles values. A const value is not a memory location. It is a template. Every time you use a const, the compiler copies the value into the current scope. This is called inlining.

const MAX_SIZE: usize = 1024;

fn process_buffer() {
    // The compiler inserts the literal 1024 here.
    let buffer = [0u8; MAX_SIZE];
}

fn check_limit() {
    // The compiler inserts the literal 1024 here too.
    if buffer.len() > MAX_SIZE {
        // ...
    }
}

This inlining has consequences. Small constants are extremely efficient because they disappear into the surrounding code. Large constants can increase binary size because the value is duplicated at every use site. If you have a massive array and you reference it in ten places, the array exists ten times in the binary.

This is why const and static are different. A static variable lives at a fixed memory address. It is initialized once, usually at program startup, and every reference points to the same location. const is a copy. static is a reference. E0015 blocks you from using runtime logic in const because const must be fully resolvable at compile time to support inlining.

Real-world pattern: computed constants

A common use case for const fn is generating configuration values or geometric data that depends on parameters but remains fixed for the lifetime of the program. You can write helper functions to keep constants readable and maintainable.

/// Represents a color in RGB format.
struct Color {
    r: u8,
    g: u8,
    b: u8,
}

/// Creates a color from grayscale intensity.
/// Marked const so it can be used in constant definitions.
const fn grayscale(intensity: u8) -> Color {
    Color {
        r: intensity,
        g: intensity,
        b: intensity,
    }
}

/// Creates a color from hex components.
/// Combines arithmetic with the grayscale helper.
const fn from_hex(r: u8, g: u8, b: u8) -> Color {
    Color { r, g, b }
}

// These constants are computed at compile time.
// The binary contains the final Color structs, not the function calls.
const WHITE: Color = grayscale(255);
const BLACK: Color = grayscale(0);
const RED: Color = from_hex(255, 0, 0);

The community convention is to add const to functions when they are pure computations that benefit from compile-time evaluation, or when you need to use them in const contexts. Do not mark every function const. It adds mental overhead for readers and restricts the function body. If you need to use a non-const feature later, you have to refactor. Start with fn, add const when the compiler forces you or when you have a specific compile-time need.

Rust is gradually expanding the set of const-compatible operations. Standard library methods like Vec::new, String::new, and many arithmetic operations have become const over recent versions. Check the documentation for the methods you use. A function that fails today might work tomorrow as Rust stabilizes more const features.

Pitfalls and hidden traps

E0015 can appear in subtle ways. The compiler checks the entire call graph. If your const fn calls another function, that function must also be const. If it calls a non-const function, you get E0015 pointing to the inner call.

const fn helper() -> i32 {
    // This function is const, so it can be used in constants.
    42
}

fn runtime_helper() -> i32 {
    // This function is not const.
    99
}

const fn mixed_call() -> i32 {
    // E0015: calls in constants are limited.
    // The error points here because runtime_helper is not const.
    helper() + runtime_helper()
}

const RESULT: i32 = mixed_call();

The fix is to make the inner function const or restructure the code to avoid the call. You cannot partially evaluate a constant. The entire expression must be resolvable at compile time.

Another trap is panics. If a const fn panics, the compilation fails. The compiler treats panics in constant evaluation as errors. This is a safety feature. It prevents you from shipping a binary with invalid constants. If your const fn divides by zero or accesses an out-of-bounds index, the compiler stops and shows you the panic message. Treat const evaluation like a unit test that runs every time you compile.

Recursion is also limited. The compiler has a maximum recursion depth for constant evaluation. Deep recursive const fns will fail with a "recursion limit reached" error. This limit exists to prevent the compiler from hanging on infinite loops. Keep const functions iterative or shallow.

Decision matrix

Choosing between const, static, and runtime initialization depends on when the value must exist and how it is used. Follow these rules to match the tool to the requirement.

Use const fn when the computation is pure, uses only primitive types or const-compatible structs, and you need the value available at compile time for arrays, enum discriminants, or other constants.

Use static when the value requires heap allocation, complex initialization, or you need a single memory address for the value shared across the program. static variables are evaluated once at program startup, not at compile time.

Use runtime initialization when the value depends on environment variables, file I/O, network calls, or any data unavailable during compilation. Move the logic into fn main or a setup function.

Use const for small values that are inlined frequently. Use static for large data structures to avoid binary bloat. Use runtime for anything dynamic.

Pick the tool that matches when the value must exist. Compile time for constants. Startup for statics. Runtime for the rest.

Where to go next