ProgrammingSystem Rust Developer

How is constant evaluation (const evaluation) implemented in Rust? How does const differ from static and let, when can (and should) const fn be used, and what are the limitations when writing computations at compile time?

Pass interviews with Hintsage AI assistant

Answer.

Constant evaluation in Rust allows for some computations or initializations to be performed at compile time rather than at runtime.

  • const declares an immutable constant that is evaluated at compile time and does not have an address in memory:
const PI: f64 = 3.1415;
  • static declares a global variable that resides in a specific memory segment (usually .data or .bss), which can be mutable (requires unsafe):
static mut GLOBAL_COUNTER: i32 = 0;
  • let is used for stack variables, their value may be computed at runtime, and they must be initialized before first use.

const fn is a function whose result can be used to assign the value of const or static. Such functions can be called in a constant context.

const fn factorial(n: usize) -> usize { if n == 0 { 1 } else { n * factorial(n - 1) } } const FACT_5: usize = factorial(5); // Compiles!

Limitations of const fn:

  • Can only use other const fn,
  • Cannot use heap allocation (Box::new etc.),
  • Cannot perform unsafe operations,
  • No access to external variables and functions (unless they are const fn).

Trick question.

Question: Can any function be used in a const context if its result does not change? For example, like this:

fn add(a: i32, b: i32) -> i32 { a + b } const RES: i32 = add(1, 2);

Typical wrong answer: Yes, since the function is pure and the result is known in advance.

Correct answer: No, the function must be explicitly declared as const fn, only then can it be called within const initialization. Regular functions are only called at runtime!

Example:

const fn add(a: i32, b: i32) -> i32 { a + b } const RES: i32 = add(1, 2); // Compiles!

Examples of real errors due to misunderstanding of the topic.


Story

In a project dealing with computations of three-dimensional geometry, a developer tried to declare a lookup table using the result of a call to a regular function instead of a const fn. As a result, compilation errors occurred and the profit from compile-time evaluation was lost.


Story

Using static mut for a global cache led to data races when accessed from multiple threads (static mut is not safe!). Atomic or Mutex should have been used to synchronize access to the global resource.


Story

In an attempt to speed up the initialization of large arrays, they were defined as static, but forgot that static always has a fixed address, which caused the data not to be cached by the processor as local, and operations slowed down on the hot path of server logic. Local let-expressions with computations at runtime should have been used.