What Is the Difference Between const and static in Rust?

Use const for compile-time constants and static for global variables that persist for the program's lifetime.

When one value isn't enough

You are building a configuration system for a game engine. You have a magic number for the maximum number of players allowed per server. That number is part of the protocol. It never changes. You also have a shared logger that every module needs to write debug messages to. The logger must exist once, and every part of the code needs to point to that same instance.

Both feel like "global things." Rust gives you two keywords: const and static. They look similar. They behave very differently under the hood. Picking the wrong one can bloat your binary, break your aliasing rules, or trigger lifetime errors that make no sense until you understand memory layout.

Const is data. Static is a location.

const defines a value that gets stamped into your code at every use site. It is a compile-time constant. The compiler sees const MAX_PLAYERS = 64 and replaces every usage with the literal 64. There is no memory address for a const. It is just data embedded in the instructions. If you use the constant in ten functions, the value is copied ten times.

static defines a value that lives at a fixed memory address for the entire lifetime of the program. It is a global variable. There is exactly one copy in memory. You can take a reference to it. You can share that reference across threads. It has an address. It occupies space in the binary's data segment.

Think of const as a stencil. Every time you use it, you spray paint the shape onto a new surface. Think of static as a painted mural on a wall. Everyone points to the same mural. You can't copy the mural; you can only point to it.

Minimal example

// const is inlined. No address exists.
const MAX_PLAYERS: u32 = 64;

// static has an address. Lives for 'static lifetime.
static ENGINE_VERSION: &str = "1.0.0";

/// Prints configuration details using both const and static.
fn print_config() {
    // MAX_PLAYERS is just a number here. The compiler inlined it.
    let limit = MAX_PLAYERS + 10;

    // ENGINE_VERSION is a reference to a global string.
    // We can take a reference because static has an address.
    let version_ref: &'static str = ENGINE_VERSION;

    println!("Limit: {limit}, Version: {version_ref}");
}

fn main() {
    print_config();
}

const values are pure data. static values are locations. If you need an address, you need static.

How the compiler handles them

When you compile, const values are evaluated immediately. If you have a complex expression, the compiler computes it. The result is pasted everywhere. This means if you write const A = 1; const B = A + 1;, B becomes 2 everywhere. There is no runtime cost. There is no indirection. The value is right there in the machine code.

const can also call const fn. This allows you to perform computation at compile time. You can write functions that calculate values and use them in const contexts. This is how you get dynamic behavior at compile time without runtime overhead.

static values are placed in the binary's data segment. When the program starts, the operating system loads that memory. The address is fixed. You can pass &static around. This is useful for sharing. The lifetime of a static is always 'static. It lives as long as the program runs. You can return a reference to a static from any function, and the reference will never dangle.

There is a subtle difference with slices. A const slice creates a temporary array every time you use it. A static slice points to global data. If you have a large array, making it const and taking a reference can cause issues because the temporary array dies at the end of the statement. Making it static ensures the data persists.

Convention aside: static items are named in SCREAMING_SNAKE_CASE by convention, just like const. This signals to readers that the item is a global constant. If you see a static in lowercase, it is likely a mistake or a very old codebase.

Const is data. Static is a location.

Realistic example: Lookup tables

Imagine a large lookup table for color palettes. You have 1000 colors, each stored as a u32. You need to access this table from many different rendering functions.

If you make it const, every function that uses the table gets a copy of the whole table. Your binary explodes. The code segment grows by 4 kilobytes for every function that references the palette. This is binary bloat.

If you make it static, there is one copy. Functions take a reference to the global table. The binary size stays small. The memory is shared.

// BAD: Binary bloat if used many times.
// Every use site copies the entire array.
// const PALETTE: [u32; 1000] = [/* ... */];

// GOOD: Single copy, shared reference.
// The array lives in the data segment.
static PALETTE: [u32; 1000] = [0xFF0000FF; 1000];

/// Returns a color from the global palette.
/// Takes a reference to the static array.
fn get_color(index: usize) -> u32 {
    // Accesses the single global copy.
    // No copying occurs.
    PALETTE[index]
}

fn main() {
    // get_color can be called from anywhere.
    // All calls share the same PALETTE.
    let red = get_color(0);
    println!("{red:#010x}");
}

Use static for large data you want to share, not copy.

Pitfalls and compiler errors

Taking a reference to const

You cannot take a reference to a const and hold it. const values are created fresh every time you use them. If you write let x = &MY_CONST;, the compiler creates a temporary value, borrows it, and then drops it immediately. You cannot store that reference.

The compiler will reject this with E0515 (cannot return value referencing temporary value) or a similar lifetime error depending on context. The error message will tell you that the temporary value does not live long enough.

const MAX_SIZE: usize = 1024;

fn bad_ref() -> &'static usize {
    // Error: E0515.
    // The const creates a temporary.
    // The temporary dies at the end of the statement.
    // The reference would dangle.
    &MAX_SIZE
}

If you need a reference that lives forever, use static.

Mutable statics

static can be mutable. static mut X: i32 = 0; compiles. Accessing it is dangerous. Reading or writing a static mut requires unsafe. The compiler cannot track aliasing. If you have two references to a mutable static, you break Rust's safety guarantees. Data races become possible.

The compiler prevents safe access to static mut. You will get E0133 (dereference of raw pointer requires unsafe) if you try to read or write it without an unsafe block.

static mut COUNTER: u32 = 0;

fn bad_increment() {
    // Error: E0133.
    // Accessing static mut requires unsafe.
    COUNTER += 1;
}

Never use static mut. Reach for std::sync::Mutex or std::cell::Cell instead. These types provide safe mutation with clear ownership rules. The unsafe overhead and aliasing risks of static mut are never worth it.

Const evaluation limits

const values must be evaluatable at compile time. You cannot use runtime values. You cannot read from a file. You cannot call a non-const function. If you try, the compiler will reject the const definition.

This is a feature. It ensures that const values are truly constant. If you need runtime initialization, use static with a lazy initialization crate like lazy_static or std::sync::LazyLock.

Const is data. Static is a location.

Decision matrix

Use const for values that are pure data and should be inlined at every use site. Use const when you need a compile-time constant for array lengths, enum discriminants, or generic parameters. Use const for small values where copying is cheap and you don't need a stable memory address.

Use static when you need a single shared instance of data with a stable memory address. Use static for large lookup tables or buffers where copying would bloat the binary. Use static when you need to share a reference across threads or modules and require the 'static lifetime.

Use static with Mutex or LazyLock when you need mutable global state that is safe to access from multiple threads.

Avoid static mut entirely. The unsafe overhead and aliasing risks are never worth it.

If you need an address, it's static. If you just need a value, it's const.

Where to go next