1. Symptoms
When your Rust code triggers error E0015, the compiler produces output similar to the following:
error[E0015]: cannot call non-const fn `some_function` in a constant
--> src/main.rs:5:5
|
5 | some_function();
| ^^^^^^^^^^^^^^^
|
= note: non-const functions may only be called at runtime, in constant
expressions in the right-hand side of `let` statements with no patterns
This error typically manifests when you attempt to use a runtime function inside a const context, whether that context is a const fn, a static variable, or a const variable declaration. The compiler explicitly rejects the operation because constant evaluation in Rust occurs entirely at compile time, and certain operations—including function calls that the compiler cannot evaluate—are not permitted in this phase.
You might encounter this error in scenarios such as initializing a static variable with a function call, computing a constant value using a helper function that performs I/O or complex runtime operations, or attempting to use a method that has side effects within a const context. The error message includes a note explaining that non-const functions may only be called at runtime, reinforcing the fundamental restriction of Rust’s const evaluation system.
Additional symptoms may include cascading errors if the const context appears inside a struct definition or enum variant, where subsequent fields might not be evaluated due to the earlier failure. These secondary errors often mask the original E0015 until you address the root cause.
2. Root Cause
The underlying mechanism behind error E0015 stems from Rust’s const evaluation model, which performs extensive compile-time computation to initialize const and static variables. The Rust compiler, rustc, executes const evaluation in a restricted environment that cannot perform arbitrary runtime operations. This restriction exists because the compiler must guarantee that constant values are truly known at compile time, enabling their use in contexts that require static initialization such as array lengths, generic parameters, and compile-time trait bounds.
When you declare a function, it exists in one of three states regarding const evaluation: it may be a regular function that performs arbitrary runtime operations, it may be marked const fn indicating the compiler can evaluate it at compile time if all called functions are also const, or it may be a truly compile-time construct like a intrinsic. A const fn is allowed in const contexts only when the function body contains only operations that the compiler can verify as safe for compile-time execution—meaning no external function calls to non-const functions, no heap allocations via Box::new or similar constructs, no synchronization primitives, and no I/O operations.
The error occurs because your code attempts to invoke a function that lacks the const qualifier within a context requiring compile-time evaluation. Even if the function happens to be deterministic and side-effect-free, the compiler cannot assume these properties unless you explicitly mark the function as const fn. This design enforces a clear separation between compile-time and runtime computations, preventing accidental reliance on runtime behavior in contexts where such behavior would be inappropriate or undefined.
3. Step-by-Step Fix
To resolve E0015, you must either make the function const-evaluable or remove the const requirement from the context. The appropriate fix depends on whether you control the function definition and whether const evaluation is actually necessary.
Step 1: Determine if you own the function
If the non-const function is from a third-party crate, you cannot directly modify it. If it’s from a standard library function not marked const, you similarly cannot change it. In these cases, skip to Step 4. If you own the function, proceed to Step 2.
Step 2: Mark the function as const fn
If the function performs only operations that are allowed in const contexts, add the const keyword to its declaration and definition:
Before:
fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
const RESULT: i32 = add_numbers(5, 10);
After:
const fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
const RESULT: i32 = add_numbers(5, 10);
Step 3: Propagate const qualification to called functions
If your const fn calls other functions, those must also be const. Check each function in the call chain and mark them accordingly:
Before:
fn double(x: i32) -> i32 {
x * 2
}
fn add_and_double(a: i32, b: i32) -> i32 {
double(a) + b
}
const VALUE: i32 = add_and_double(3, 4);
After:
const fn double(x: i32) -> i32 {
x * 2
}
const fn add_and_double(a: i32, b: i32) -> i32 {
double(a) + b
}
const VALUE: i32 = add_and_double(3, 4);
Step 4: Remove const requirement when const is not achievable
If the function cannot be made const (perhaps it calls external code, performs I/O, or uses runtime-only features), refactor your code to compute the value at runtime instead:
Before:
fn get_default_value() -> i32 {
println!("Computing default value...");
42
}
static CACHED_VALUE: i32 = get_default_value();
After:
fn get_default_value() -> i32 {
println!("Computing default value...");
42
}
fn main() {
let cached_value = get_default_value();
// Use cached_value in your program logic
}
Step 5: Use lazy initialization for static values requiring runtime computation
For static variables that genuinely need runtime initialization, use the once_cell crate or the standard library’s Mutex for thread-safe runtime initialization:
use std::sync::Mutex;
static COMPUTED_VALUE: Mutex<Option<i32>> = Mutex::new(None);
fn main() {
let mut guard = COMPUTED_VALUE.lock().unwrap();
if guard.is_none() {
*guard = Some(expensive_computation());
}
println!("Value: {}", guard.unwrap());
}
4. Verification
After applying your fix, verify that the error is resolved by recompiling your project:
cargo build
A successful compilation produces no E0015 errors, and your constant or static variable initializes correctly. For const functions, verify that the function is actually usable in const contexts by adding additional test cases:
const fn add(a: i32, b: i32) -> i32 {
a + b
}
const SUM: i32 = add(10, 20);
const ALSO_SUM: i32 = add(SUM, 30); // Verify const chains work
fn main() {
assert_eq!(SUM, 30);
assert_eq!(ALSO_SUM, 60);
}
Run your tests to confirm the computed values are correct:
cargo test
If you used runtime initialization for previously static values, verify that the initialization occurs exactly once (for once_cell patterns) or that your application behaves correctly with the new initialization pattern. Test edge cases where the computed value might differ between compilations to ensure consistency.
5. Common Pitfalls
One frequent mistake is assuming that a simple, pure function can be called in const context without marking it const. Rust’s type system requires explicit const qualification for const evaluation, even for functions that are logically pure. The compiler cannot infer constness from function behavior alone because doing so would require solving the halting problem or similar undecidable properties.
Another pitfall involves nested const requirements. When converting a function to const, you must also convert every function it calls. Forgetting this transitive requirement leads to E0015 appearing on seemingly const-safe code. Use cargo expand or thorough testing to verify that all call chains are properly marked.
Be cautious with third-party dependencies. Some commonly used functions in the standard library are not const, and many third-party crate functions cannot be const. When designing APIs that need const availability, document this requirement clearly and ensure all internal implementations support const evaluation.
Avoid using const when static is semantically more appropriate. const creates an inlined value at each use site, while static creates a single instance in the binary. Using the wrong storage class can cause unexpected behavior or performance issues, though this does not directly cause E0015.
Finally, remember that trait methods require special handling. Calling trait methods in const contexts requires the trait itself to be marked const and all implementations to provide const methods. Standard library traits like Clone and Default have const constructors in newer Rust versions, but older crates may not support this.
6. Related Errors
E0013: This error indicates that const evaluation is disabled for the selected edition or crate type. Unlike E0015 which concerns function calls, E0013 relates to the overall const evaluation feature being unavailable, often encountered in #![no_std] environments or older Rust editions.
E0435: This error occurs when attempting to use a non-constant expression in a const context, such as calling a function that is not marked const. E0435 is closely related to E0015 and often appears alongside it when complex const expressions involve multiple invalid operations.
E0740: This error emerges when a const variable contains a value that is not statically known. While E0015 focuses on function calls, E0740 addresses broader const evaluation failures where the compiler cannot determine a value’s contents at compile time, such as when const contains a type with interior mutability.
Understanding the distinction between these related errors helps you diagnose const-related issues more effectively. E0015 specifically targets function calls, while E0435 addresses general const expression requirements, and E0740 concerns value initialization. When multiple const errors appear simultaneously, address the earliest one in the compiler’s analysis—often E0015 or E0435—to resolve cascading failures.