We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Performance and Data Layout step 20 of 20
const fn and compile-time evaluation
The purest form of “pay at build time, not run time”: code that produces a value during compilation and contributes zero instructions to the running program. And the place where you meet the const-eval engine as what it really is — a language within the language, with its own rules about what may run.
What to write
pub const fn crc32_table() -> [u32; 256]
pub static TABLE: [u32; 256] = crc32_table();
pub fn crc32(data: &[u8]) -> u32
The table is the standard CRC-32 (IEEE 802.3, reflected, polynomial
0xEDB88320). Entry i is computed by starting from i and, eight times:
c = if c & 1 != 0 { 0xEDB8_8320 ^ (c >> 1) } else { c >> 1 }
crc32 is the table-driven loop: start at 0xFFFF_FFFF, and for each byte
crc = TABLE[((crc ^ byte) & 0xFF) as usize] ^ (crc >> 8)
then return crc ^ 0xFFFF_FFFF. The classic check value applies:
crc32(b"123456789") == 0xCBF4_3926.
The spec also requires a compile-time assertion:
const _: () = assert!(crc32_table()[1] == 0x7707_3096);
The error that stops you first: E0015
You will write the table with a for loop, and it will not compile.
error[E0015]: cannot call non-const method
`<std::ops::Range<usize> as Iterator>::next` in constant functions
A for loop desugars to IntoIterator::into_iter plus repeated
Iterator::next — trait method calls, and trait dispatch does not run in
const context. while does. So does loop. Rewrite the loop with an explicit
counter and it works.
That is the shape of the whole const-eval sublanguage: no iterators, no heap,
no trait methods (with a growing list of exceptions), no floating-point in
some positions, no reading of statics. What it does have is arithmetic,
arrays, if, while, match, indexing and mutation of locals — which is
enough to build almost any table you want.
Why the assertion calls the function instead of reading TABLE
Because this fails:
const _: () = assert!(TABLE[1] == 0x7707_3096); // E0013
E0013: constants cannot refer to statics. A const is a value inlined at
every use site, while a static is a memory location; letting one read the
other would mean the compiler had to reason about a runtime address at compile
time. Call crc32_table() again instead — it costs nothing, because it runs
at compile time too.
const or static for a table?
static, and it matters.
A const item is not a variable. It is a value inlined at every use site —
so const TABLE: [u32; 256] would copy all 1024 bytes into the binary at each
mention, and worse, taking &TABLE in two places could give two different
addresses. A static has one address and one copy.
This is precisely what clippy’s large_const_arrays lint catches, and it is
one of the few clippy lints whose reasoning is about the binary rather than
about style.
A pitfall that is genuinely surprising
const fn fib(n: u64) -> u64 {
if n < 2 { n } else { fib(n - 1) + fib(n - 2) }
}
const F30: u64 = fib(30);
This fails to compile:
error: constant evaluation is taking a long time
The long_running_const_eval lint is deny by default. The const-eval
interpreter is a tree-walking interpreter — orders of magnitude slower than
the compiled code would be — and it stops you rather than hanging your build.
The iterative version computing fib(90) compiles instantly. The lesson is
that const-eval is for tables and small computations, not for moving your
workload to build time.
Two more modern conveniences
-
const { ... }blocks (stable 1.79) force an expression to be evaluated at compile time, anywhere — including inside a function body. They are the modern way to write a compile-time assertion:const { assert!(N.is_power_of_two()) }. -
const fnis not the same as “always evaluated at compile time.” Aconst fnmay also be called at runtime, with ordinary arguments, and then it is just a function.const/staticinitialisers andconst { }blocks are what force evaluation.
Errors and lints
E0015 (a non-const call in const context), E0080 (evaluation of a
constant value failed — a failing const assert), E0013 (a const
referring to a static), E0493 (a destructor cannot run in const context),
E0658 (an unstable const feature). Lints: large_const_arrays,
missing_const_for_fn (pedantic), missing_const_for_thread_local, and the
interior-mutability pair declare_interior_mutable_const and
borrow_interior_mutable_const — a const AtomicUsize is a fresh, separate
atomic at every use site, which is never what anybody wants.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.