---
title: "5.1 Constants and statics"
description: "Declaring compile-time constants with const, naming them, hunting magic numbers, and the two paragraphs static deserves."
url: "https://learnrust.net/chapter-5/constants-and-statics/"
last_updated: "2026-06-12"
---

# 5.1 Constants and statics

Your falling-ball program from the [chapter 4 quiz](https://learnrust.net/chapter-4/chapter-4-summary-and-quiz/) contains the line `let fallen = 9.8 * seconds * seconds / 2.0;`. That `9.8` is the acceleration due to gravity, in meters per second squared, and it has been the same number since long before anyone wrote programs about it. Storing it in an ordinary variable would work, but an ordinary variable undersells the situation: nothing about `let gravity = 9.8;` tells the reader (or the compiler) that this value is *never* supposed to change. Rust has a dedicated tool for exactly this, and lesson [1.8](https://learnrust.net/chapter-1/keywords-and-identifiers/) promised it: constants, the kind with a naming convention of their own. The ball's `9.8` is about to get a name.

## Declaring a constant

A **constant** is a named value fixed for the entire life of the program, declared with the `const` keyword:

```rust
const EARTH_GRAVITY: f64 = 9.8;

fn fall_distance(seconds: f64) -> f64 {
    EARTH_GRAVITY * seconds * seconds / 2.0
}

fn main() {
    println!("After 3 seconds, a dropped ball has fallen {} meters.", fall_distance(3.0));
    println!("Earth's gravity is {EARTH_GRAVITY} m/s^2.");
}
```

```
After 3 seconds, a dropped ball has fallen 44.1 meters.
Earth's gravity is 9.8 m/s^2.
```

Three things in that program are new. First, the syntax: `const`, a name, a type annotation, a value. Second, the location: the constant lives *outside* any function, the first time this course has put anything out there, and both functions can see it. Constants are allowed in any scope, including that outermost one (called *global* scope); a constant only needed inside one function can be declared inside it.

Third, the annotation `: f64` isn't a courtesy. For constants, the type is required, and the compiler holds the line:

```rust
const EARTH_GRAVITY = 9.8;

fn main() {
    println!("{EARTH_GRAVITY}");
}
```

```
error: missing type for `const` item
 --> src/main.rs:1:20
  |
1 | const EARTH_GRAVITY = 9.8;
  |                    ^ help: provide a type for the constant: `: f64`
```

Lesson [4.9](https://learnrust.net/chapter-4/type-inference-and-annotations/) explained why signatures stay explicit while locals get inferred: things visible from far away should say what they are. A global constant is about as visible-from-far-away as Rust gets, so it documents itself, by law.

## Known at compile time

A constant's value must be something the compiler can work out *while compiling*, before the program ever runs. Literals qualify, and so does arithmetic on literals and other constants:

```rust
const SECONDS_PER_MINUTE: u32 = 60;
const SECONDS_PER_HOUR: u32 = SECONDS_PER_MINUTE * 60;
const SECONDS_PER_DAY: u32 = SECONDS_PER_HOUR * 24;

fn main() {
    println!("A day has {SECONDS_PER_DAY} seconds.");
}
```

```
A day has 86400 seconds.
```

The multiplication happens at compile time; the finished program contains `86400` and does no arithmetic at all. You've actually watched this compile-time evaluation machinery at work twice already, both times as a refusal: lesson [3.1](https://learnrust.net/chapter-3/syntax-and-semantic-errors/)'s provable divide-by-zero and lesson [4.4](https://learnrust.net/chapter-4/integer-overflow/)'s provable overflow were both rejected *at compile time* because the compiler computed the constant expressions and didn't like what it found.

What can't be a constant is anything only knowable at runtime:

```rust
fn main() {
    let height = 100;
    const TOWER_HEIGHT: i32 = height;
    println!("{TOWER_HEIGHT}");
}
```

```
error[E0435]: attempt to use a non-constant value in a constant
 --> src/main.rs:3:31
  |
3 |     const TOWER_HEIGHT: i32 = height;
  |                               ^^^^^^ non-constant value
  |
help: consider using `let` instead of `const`
  |
3 -     const TOWER_HEIGHT: i32 = height;
3 +     let TOWER_HEIGHT: i32 = height;
  |
```

`height` is an ordinary variable, its value a runtime matter (trivially so here, but the rule doesn't read minds), and `const` won't have it. This is the real division of labor: an immutable `let` is "computed while running, then frozen"; a `const` is "settled before the program exists." User input, for instance, can never be `const`, no matter how immutable it is once read.

> **Author's note**
>
> If you're arriving from C++: this lesson is suspiciously short, and that's the point. C++'s `const` doesn't guarantee compile-time evaluation, so learncpp needs a three-lesson arc (the as-if rule, constant expressions, the `constexpr` keyword) to build up to a variable that's *reliably* a compile-time constant. Rust's `const` is `constexpr` by birth. The entire arc collapses into the paragraph you just read.

> **Key insight**
>
> Every value that *can* change is a moving part, something a reader must track and a bug can hide behind. Constants and immutable `let` bindings are inert: once you've read the declaration, you're done thinking about them. This is why Rust made immutable the default (lesson [1.4](https://learnrust.net/chapter-1/variables-mutability-initialization/)) and why you should reach for `const` whenever a value is truly fixed: it's one less moving part, and it announces itself.

## Naming: the shouting convention

Lesson [1.8](https://learnrust.net/chapter-1/keywords-and-identifiers/) promised that constants get `SCREAMING_SNAKE_CASE`, and that the compiler nudges you about conventions personally. Time to collect on both:

```rust
const earth_gravity: f64 = 9.8;

fn main() {
    println!("{earth_gravity}");
}
```

```
warning: constant `earth_gravity` should have an upper case name
 --> src/main.rs:1:7
  |
1 | const earth_gravity: f64 = 9.8;
  |       ^^^^^^^^^^^^^
  |
  = note: `#[warn(non_upper_case_globals)]` (part of `#[warn(nonstandard_style)]`) on by default
help: convert the identifier to upper case
  |
1 - const earth_gravity: f64 = 9.8;
1 + const EARTH_GRAVITY: f64 = 9.8;
  |
```

The program compiles and runs, but the warning is right and you should obey it. The shouting is a feature: when `MAX_PLAYERS` appears in the middle of a function, the capitals announce "fixed value, declared elsewhere, don't look for an assignment" before you've consciously read the name.

## Magic numbers

Constants also fix a code smell you've already produced (the course made you do it, in fairness). A **magic number** is a bare literal whose meaning isn't obvious from context. Consider a school program:

```rust
fn main() {
    let classrooms = 4;
    let max_students = classrooms * 30;
    println!("The school can hold {max_students} students.");
    println!("Hiring {} teachers.", 4 * 30 / 15);
}
```

What's `30`? Students per classroom, probably. Is the `30` on line 5 the *same* thirty, or a different fact that happens to share a digit pattern? If the classroom size changes to 25, how many of these numbers do you edit? You can't tell without re-deriving the entire program in your head, and neither can the colleague who inherits it. Named constants delete the guesswork:

```rust
const STUDENTS_PER_CLASSROOM: i32 = 30;
const STUDENTS_PER_TEACHER: i32 = 15;

fn main() {
    let classrooms = 4;
    let max_students = classrooms * STUDENTS_PER_CLASSROOM;
    println!("The school can hold {max_students} students.");
    println!("Hiring {} teachers.", max_students / STUDENTS_PER_TEACHER);
}
```

Now every number states its meaning, the duplicated fact lives in exactly one place, and a policy change is a one-line edit. Note the second `println!` also got more honest: writing it in terms of `max_students` exposed that it always depended on the same quantity.

Not every literal is magic. Obvious conversion factors (`* 1000.0` for kilometers to meters), the `2.0` in a halving, a loop's starting `0`: these explain themselves, and naming them (`const TWO: f64 = 2.0;` is a real thing people have committed) adds noise, not clarity.

> **Best practice**
>
> Give a name to any literal whose meaning isn't instantly obvious, *especially* if it appears more than once. Prefer `const` for the name, at the narrowest scope that covers all its uses.

## The two paragraphs static deserves

Rust has a second keyword for fixed global values: `static`. The declaration looks like `const` with the keyword swapped (`static GREETING: &str = "hello";`), and the difference is about identity. A `const` has no fixed home in memory; the compiler copies its value into every place it's used, the way `86400` got baked in earlier. A `static` is the opposite: one value, at one address, for the program's whole life, and every mention refers to that single home.

When would the address matter? Not yet, and not for a while: the honest examples involve interior mutability (chapter 21) and talking to other languages (chapter 25). Until a situation actively demands one home in memory, the working rule is short: use `const`, and read `static` in other people's code as "like a const, but it has an address."

## Quiz time

**Question #1**

For each value, the best declaration: `const`, immutable `let`, or `let mut`?

a) the number of milliseconds in a second
b) a player's current score
c) the tower height typed in by the user, used but never changed afterward
d) the maximum number of login attempts your program allows

<details class="solution">
<summary>Show solution</summary>

a) `const`: fixed forever, known while writing the program.
b) `let mut`: scores exist to change.
c) immutable `let`: never changes once read, but it arrives at runtime, so `const` is impossible (the compiler will say so, with `E0435`).
d) `const`: it's a policy you decided at your desk, exactly the "settled before the program exists" case. Name it like `MAX_LOGIN_ATTEMPTS` and the capitals will do the explaining.

</details>

**Question #2**

This program works. Improve it without changing its output:

```rust
fn main() {
    let baskets = 3;
    println!("Apples: {}", baskets * 12);
    println!("If one basket spoils: {}", (baskets - 1) * 12);
}
```

<details class="solution">
<summary>Show solution</summary>

```rust
const APPLES_PER_BASKET: i32 = 12;

fn main() {
    let baskets = 3;
    println!("Apples: {}", baskets * APPLES_PER_BASKET);
    println!("If one basket spoils: {}", (baskets - 1) * APPLES_PER_BASKET);
}
```

The `12` appeared twice with no hint that both meant "apples per basket," and a change to basket size would have required finding every copy. The `1` in `baskets - 1`, by contrast, is self-explanatory ("one fewer basket") and stays a literal.

</details>

**Question #3**

Without compiling: what does the compiler say about this program, and does it run?

```rust
const max_score: u32 = 100;

fn main() {
    println!("Top score: {max_score}");
}
```

<details class="solution">
<summary>Show solution</summary>

It compiles and runs (printing `Top score: 100`), but the compiler issues a warning: constant `max_score` should have an upper case name, with a `help` line offering `MAX_SCORE` and a note that `#[warn(non_upper_case_globals)]` is on by default. Conventions aren't errors, but lesson [0.11](https://learnrust.net/chapter-0/warnings-lints-and-clippy/)'s policy stands: a warning is a to-do item, not a decoration.

</details>

One question this lesson raised quietly: if `let` bindings are immutable, how has `let trimmed = name.trim();` been making *new* variables for the same data since lesson 1.6, and is there a tidier way? There is, it has a name, and it's next.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
