Fixing Rust Error E0015: Why You Can't Call Functions in Constants

intermediate🦀 Rust2026-06-28| Rust (Stable 1.80+), Cargo, any OS

Error Message

error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
#rust#compiler-error#const#lazylock

The ProblemComing from languages like Python or JavaScript, you might expect to initialize a global configuration map by simply calling a constructor. Rust, however, is much more protective of the compilation process. It distinguishes strictly between what happens when you build your code and what happens when it runs.

Here is the snippet that usually triggers this headache:

use std::collections::HashMap;

// This will trigger E0015
const MY_CONFIG: HashMap = HashMap::new();

fn main() {
    println!("{:?}", MY_CONFIG);
}

Run cargo build, and the compiler will stop you with this message:

error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
 --> src/main.rs:4:39
  |
4 | const MY_CONFIG: HashMap = HashMap::new();
  |                                       ^^^^^^^^^^^^^^
  |
  = note: calls in constants are limited to constant functions, tuple structs and tuple variants

Why This HappensThe const keyword tells Rust to calculate a value once at compile time and inline it everywhere it is used. To do this safely, the compiler requires that any function called within a const block is marked as a const fn. These are deterministic functions that the compiler can execute without running your full program.

You might wonder why HashMap::new() isn't a const fn. The reason is that a HashMap uses a random seed for its hashing algorithm to prevent DoS attacks. Generating that randomness requires interacting with the operating system, which the compiler cannot do while it's still building your binary.

Solution 1: Use LazyLock (The Modern Standard)If you are using Rust 1.80 or newer, std::sync::LazyLock is your best friend. It allows you to define a global variable that waits to initialize until the exact moment you first access it. This bypasses compile-time restrictions while remaining thread-safe.

use std::collections::HashMap;
use std::sync::LazyLock;

// LazyLock initializes only when MY_CONFIG is first used
static MY_CONFIG: LazyLock> = LazyLock::new(|| {
    let mut m = HashMap::new();
    m.insert("api_version", 2);
    m.insert("timeout_ms", 5000);
    m
});

fn main() {
    // The HashMap is actually created here
    println!("Timeout: {:?}", MY_CONFIG.get("timeout_ms"));
}

Note that we switched from const to static. This ensures the data lives in a single, fixed memory location rather than being copied everywhere.

Solution 2: Use OnceLock for Dynamic DataSometimes you don't know the values of your global variable until the program is already running—perhaps you're reading a DATABASE_URL from an environment variable. In these cases, std::sync::OnceLock is the better choice.

use std::collections::HashMap;
use std::sync::OnceLock;

static APP_CACHE: OnceLock> = OnceLock::new();

fn main() {
    // Initialize the cache once at runtime
    let cache = APP_CACHE.get_or_init(|| {
        let mut m = HashMap::new();
        m.insert("session_id".to_string(), "abc-123".to_string());
        m
    });

    println!("Current session: {}", cache["session_id"]);
}

Solution 3: Converting Custom Functions to 'const'If you hit E0015 while calling your own function, you can often fix it by adding the const modifier. This works as long as your function doesn't perform heap allocation or complex I/O.

// Adding 'const' allows this to run during compilation
const fn get_max_threads(cores: u32) -> u32 {
    cores * 2
}

const WORKER_LIMIT: u32 = get_max_threads(8);

fn main() {
    println!("Max workers: {}", WORKER_LIMIT);
}

Solution 4: Use Static Arrays for Zero OverheadFor small, fixed lookups, a HashMap might be overkill. A static array of tuples is often faster and much easier for the compiler to handle. This approach uses zero runtime initialization time.

// A simple array of pairs is often enough for constants
const SERVER_SETTINGS: [(&str, u16); 3] = [
    ("web", 8080),
    ("api", 9000),
    ("metrics", 9100),
];

fn main() {
    for (service, port) in SERVER_SETTINGS {
        println!("Service {} is on port {}", service, port);
    }
}

Summary & Prevention- Avoid heap types in const: Don't use String, Vec, or HashMap directly in a const.- Reach for LazyLock: It is the cleanest way to handle global state in modern Rust.- Check the docs: If a function isn't explicitly marked const fn in the standard library, you cannot use it in a const declaration.- Performance tip: If you need a high-performance, immutable map at compile time, look into the phf (Perfect Hash Function) crate.

Related Error Notes