Constants with const
A constant is a named value that is computed at compile time and never changes. It is declared with the const keyword, must have an explicit type, and can be declared in any scope, including outside every function.
const MAX_PLAYERS: u32 = 4;
const PI_APPROX: f64 = 3.14159;
const GREETING: &str = "Hello";
const SECONDS_PER_DAY: u32 = 60 * 60 * 24;
const PRIMES: [u32; 4] = [2, 3, 5, 7];
fn main() {
println!("{MAX_PLAYERS} {PI_APPROX} {GREETING} {SECONDS_PER_DAY} {PRIMES:?}");
}
4 3.14159 Hello 86400 [2, 3, 5, 7]
Syntax
const NAME: Type = constant_expression;
| Rule | Detail |
|---|---|
| Keyword | const replaces let |
| Type | Always required. The compiler does not infer the type of a constant |
| Value | Must be a constant expression, one the compiler can evaluate at compile time |
| Name | SCREAMING_SNAKE_CASE by convention |
| Mutability | Never mutable. mut is not allowed |
| Placement | Module level (global) or inside any block, including function bodies |
The Type Is Required
// ❌ does not compile
const LIMIT = 100;
fn main() {
println!("{LIMIT}");
}
error: missing type for `const` item
--> main.rs:2:12
|
2 | const LIMIT = 100;
| ^ help: provide a type for the constant: `: i32`
Constants are part of an item's public interface and can be used from anywhere, so their types are always written out.
Constants Cannot Be mut
// ❌ does not compile
const mut LIMIT: u32 = 100;
fn main() {
println!("{LIMIT}");
}
error: const globals cannot be mutable
--> main.rs:2:7
|
2 | const mut LIMIT: u32 = 100;
| ^^^ cannot be mutable
|
help: you might want to declare a static instead
|
2 - const mut LIMIT: u32 = 100;
2 + static mut LIMIT: u32 = 100;
|
Assigning to a constant is also an error:
// ❌ does not compile
const LIMIT: u32 = 100;
fn main() {
LIMIT = 200;
}
error[E0070]: invalid left-hand side of assignment
--> main.rs:5:11
|
5 | LIMIT = 200;
| ----- ^
| |
| cannot assign to this expression
Naming
Constants use SCREAMING_SNAKE_CASE. Other casing compiles but produces a warning:
const lower: u32 = 1;
fn main() {
println!("{lower}");
}
warning: constant `lower` should have an upper case name
--> main.rs:1:7
|
1 | const lower: u32 = 1;
| ^^^^^
|
= note: `#[warn(non_upper_case_globals)]` (part of `#[warn(nonstandard_style)]`) on by default
help: convert the identifier to upper case
|
1 - const lower: u32 = 1;
1 + const LOWER: u32 = 1;
|
The naming rule has a functional side. In a pattern, an uppercase name that resolves to a constant is treated as a constant to compare against, not as a new variable. A let that reuses a constant's name therefore does not create a new variable:
// ❌ does not compile
const LIMIT: u32 = 100;
fn main() {
let LIMIT = 5;
println!("{LIMIT}");
}
error[E0005]: refutable pattern in local binding
--> main.rs:5:9
|
2 | const LIMIT: u32 = 100;
| ---------------- missing patterns are not covered because `LIMIT` is interpreted as a constant pattern, not a new variable
...
5 | let LIMIT = 5;
| ^^^^^ patterns `0_u32..=99_u32` and `101_u32..=u32::MAX` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
= note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html
= note: the matched value is of type `u32`
help: introduce a variable instead
|
5 | let LIMIT_var = 5;
| ++++
Constant Expressions
The right-hand side of a const is evaluated by the compiler, so it can only use things the compiler is able to evaluate:
- literals
- arithmetic, bitwise, comparison, and logical operators
- other constants
ascasts between primitive types- tuples, arrays, struct and enum construction
- calls to
const fnfunctions, and to tuple struct and tuple variant constructors if,match,loop, andwhile, inside a block- associated constants of types, such as
u32::MAX
const fn square(n: u32) -> u32 {
n * n
}
const AREA: u32 = square(12);
const BUFFER_SIZE: usize = 1 << 10;
const MASK: u8 = !0b0000_1111;
const IS_BIG: bool = AREA > 100;
const LETTER: char = 'x';
const MAX_AS_I64: i64 = u32::MAX as i64;
fn main() {
let buffer = [0u8; BUFFER_SIZE];
println!("{AREA} {} {MASK} {IS_BIG} {LETTER} {MAX_AS_I64}", buffer.len());
}
144 1024 240 true x 4294967295
BUFFER_SIZE is used as an array length, which must be known at compile time. Only a constant can fill that role; a let variable cannot.
A block can hold a multi-step computation, with local let mut variables and loops:
const SUM: u32 = {
let mut total = 0;
let mut i = 1;
while i <= 10 {
total += i;
i += 1;
}
total
};
const LEN: usize = "hello".len();
fn main() {
println!("{SUM} {LEN}");
}
55 5
"hello".len() works because str::len is a const fn. Compile-time evaluation, including const fn, is covered in its own section.
What Is Not Allowed
A call to an ordinary function is rejected, because the compiler cannot run it at compile time:
// ❌ does not compile
fn limit() -> u32 {
100
}
const LIMIT: u32 = limit();
fn main() {
println!("{LIMIT}");
}
error[E0015]: cannot call non-const function `limit` in constants
--> main.rs:6:20
|
6 | const LIMIT: u32 = limit();
| ^^^^^^^
|
note: function `limit` is not const
--> main.rs:2:1
|
2 | fn limit() -> u32 {
| ^^^^^^^^^^^^^^^^^
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
Marking limit as const fn fixes it. Standard library functions that allocate or are not marked const are rejected the same way:
// ❌ does not compile
const NAME: String = String::from("rust");
fn main() {
println!("{NAME}");
}
error[E0015]: cannot call non-const associated function `<String as From<&str>>::from` in constants
--> main.rs:2:22
|
2 | const NAME: String = String::from("rust");
| ^^^^^^^^^^^^^^^^^^^^
|
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
Runtime values cannot be used either, even in a const declared inside a function:
// ❌ does not compile
fn main() {
let base = 10;
const DOUBLE: i32 = base * 2;
println!("{DOUBLE}");
}
error[E0435]: attempt to use a non-constant value in a constant
--> main.rs:4:25
|
4 | const DOUBLE: i32 = base * 2;
| ^^^^ non-constant value
|
help: consider using `let` instead of `const`
|
4 - const DOUBLE: i32 = base * 2;
4 + let DOUBLE: i32 = base * 2;
|
A local const is an item. It does not capture local variables.
Arithmetic that overflows inside a constant is a compile-time error, not a runtime panic:
// ❌ does not compile
const BIG: u8 = 255 + 1;
fn main() {
println!("{BIG}");
}
error[E0080]: attempt to compute `u8::MAX + 1_u8`, which would overflow
--> main.rs:2:17
|
2 | const BIG: u8 = 255 + 1;
| ^^^^^^^ evaluation of `BIG` failed here
(The compiler also prints two erroneous constant encountered notes pointing at the uses of BIG.)
Some types are allowed in constants even though building them at runtime allocates. The empty constructors String::new() and Vec::new() are const fn, and no allocation happens until the first push:
const EMPTY_STRING: String = String::new();
const EMPTY_VEC: Vec<i32> = Vec::new();
fn main() {
let mut s = EMPTY_STRING;
s.push('a');
let mut v = EMPTY_VEC;
v.push(1);
println!("{s} {v:?}");
}
a [1]
Each use of EMPTY_VEC produces a fresh empty vector.
Constants Are Inlined
A constant has no memory location of its own. The compiler substitutes the value at every place the name is used, as if the value had been written there. Every use is an independent copy.
const LIMIT: u32 = 100;
fn main() {
let a = LIMIT + 1; becomes let a = 100 + 1;
let b = LIMIT * 2; becomes let b = 100 * 2;
}
For small values this is free. For a large constant, such as a big array, each use can be a separate copy. When one fixed address is needed, or when a large value should be stored once, use a static item.
Interior Mutability Surprise
Because each use of a constant is a fresh copy, a constant whose type has interior mutability does not behave like shared state. This program increments COUNTER twice, and still reads 0:
use std::sync::atomic::{AtomicUsize, Ordering};
const COUNTER: AtomicUsize = AtomicUsize::new(0);
static REAL_COUNTER: AtomicUsize = AtomicUsize::new(0);
fn main() {
COUNTER.fetch_add(1, Ordering::SeqCst);
COUNTER.fetch_add(1, Ordering::SeqCst);
println!("const: {}", COUNTER.load(Ordering::SeqCst));
REAL_COUNTER.fetch_add(1, Ordering::SeqCst);
REAL_COUNTER.fetch_add(1, Ordering::SeqCst);
println!("static: {}", REAL_COUNTER.load(Ordering::SeqCst));
}
const: 0
static: 2
The compiler warns about this pattern:
warning: mutation of an interior mutable `const` item with call to `fetch_add`
--> main.rs:7:5
|
7 | COUNTER.fetch_add(1, Ordering::SeqCst);
| -------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `COUNTER` is a interior mutable `const` item of type `Atomic<usize>`
|
= note: each usage of a `const` item creates a new temporary
= note: only the temporaries and never the original `const COUNTER` will be modified
Shared state that must be observed from several places belongs in a static.
Scope and Placement
A constant can be declared at module level, where it is visible throughout the module (and to other modules with pub), or inside any block. Constants may refer to other constants regardless of their order of declaration at module level.
const FIRST: u32 = 10;
const SECOND: u32 = FIRST * 2;
fn main() {
const LOCAL: u32 = SECOND + 1;
println!("{FIRST} {SECOND} {LOCAL}");
{
const INNER: &str = "inner";
println!("{INNER}");
}
}
10 20 21
A local constant is visible only inside its block:
// ❌ does not compile
fn main() {
{
const INNER: u32 = 1;
}
println!("{INNER}");
}
error[E0425]: cannot find value `INNER` in this scope
--> main.rs:6:16
|
6 | println!("{INNER}");
| ^^^^^ not found in this scope
Unlike a let variable, a const item can be used before the line that declares it, because items are visible throughout their whole scope.
String and Reference Constants
References in constants have the 'static lifetime. The lifetime can be omitted in a const declaration, and the compiler fills in 'static:
const GREETING: &'static str = "hi";
const OTHER: &str = "hello";
fn main() {
println!("{GREETING} {OTHER}");
}
hi hello
Slices of fixed data are written as references to array literals:
const NAMES: &[&str] = &["ada", "grace", "linus"];
const ORIGIN: (i32, i32) = (0, 0);
const EMPTY: Option<u32> = None;
fn main() {
for name in NAMES {
println!("{name}");
}
println!("{ORIGIN:?} {EMPTY:?} {}", NAMES.len());
}
ada
grace
linus
(0, 0) None 3
Associated Constants
Types and traits can have constants too. An associated constant is declared with const inside an impl or trait block and is accessed with Type::NAME.
struct Circle {
radius: f64,
}
impl Circle {
const PI: f64 = 3.14159;
const UNIT: Circle = Circle { radius: 1.0 };
fn area(&self) -> f64 {
Self::PI * self.radius * self.radius
}
}
trait Shape {
const SIDES: u32;
}
struct Triangle;
struct Square;
impl Shape for Triangle {
const SIDES: u32 = 3;
}
impl Shape for Square {
const SIDES: u32 = 4;
}
fn main() {
println!("{} {}", Circle::PI, Circle::UNIT.area());
println!("{} {}", Triangle::SIDES, <Square as Shape>::SIDES);
}
3.14159 3.14159
3 4
A trait can declare an associated constant without a value, and each implementing type supplies its own. The standard library's primitive types also have inherent associated constants, such as u32::MAX, i32::MIN, and f64::EPSILON.
Compile-Time Assertions
A constant with the name _ is evaluated but cannot be referred to. Combined with assert!, it checks a condition at compile time and stops compilation if the condition is false:
const _: () = assert!(std::mem::size_of::<u64>() == 8);
fn main() {
println!("checked at compile time");
}
checked at compile time
If the condition is false, the build fails:
// ❌ does not compile
const _: () = assert!(std::mem::size_of::<u64>() == 4);
fn main() {}
error[E0080]: evaluation panicked: assertion failed: std::mem::size_of::<u64>() == 4
--> main.rs:2:15
|
2 | const _: () = assert!(std::mem::size_of::<u64>() == 4);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_` failed here
const Versus let
let |
const |
|
|---|---|---|
| Type annotation | Optional | Required |
| Value computed | At runtime | At compile time |
Can be mut |
Yes | No |
| Allowed outside functions | No | Yes |
| Can use runtime values | Yes | No |
| Can be an array length | No | Yes |
| Has a memory address | Yes (a stack slot) | No, the value is inlined at each use |
Can be shadowed by a later let of the same name |
Yes | No, a let with that name is treated as a pattern |
Use const for fixed values that are known when the program is written: limits, sizes, mathematical constants, default settings, and lookup tables.