RFA-438 · Case file with fixtures · Case 410 of 694 · Compiler evidence
Trait Methods Cannot Be Declared const on Stable Rust
Const evaluation is part of a function's callable contract, and stable Rust does not express that contract through ordinary trait methods. Use an associated const for data, a non-const method for runtime dispatch, or a concrete const fn where static selection is acceptable.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- Const-callability and trait polymorphism are separate contracts, and ordinary stable trait syntax does not promise that an implementation can be invoked by the const evaluator.
- First discriminating check
- Choose an associated const for fixed data, a normal method for runtime dispatch, or a concrete inherent const fn for compile-time callers.
I wanted a version function callable during constant evaluation and placed const fn inside a trait. Rust 1.98.1 rejected the declaration with E0379: functions in traits cannot be declared const.
The failing fixture needs no implementation. The trait declaration itself asks stable Rust to combine trait selection with a const-call guarantee that ordinary trait syntax does not provide.
const is a caller-visible guarantee
A const fn can be called at runtime like a normal function, but it may also be evaluated in constant contexts when its operations satisfy const-evaluation rules.
This is not merely an optimisation hint. Callers building a const or static value depend on the function being accepted by the const evaluator.
The E0379 explanation states that trait methods cannot be declared const. The Reference likewise says trait functions are not allowed to be const.
Use a normal trait function for runtime polymorphism
The repaired fixture removes const and implements Version::version normally. It can be called through static trait selection at runtime.
This is correct when the value is needed during execution and polymorphism matters more than compile-time construction.
Removing const is not harmless if existing callers genuinely need a constant expression. Those callers require another API shape rather than a keyword deletion alone.
Use an associated const for fixed data
If each implementation supplies one fixed version number, an associated constant is often clearer:
trait Version {
const VERSION: u32;
}
Generic code can refer to T::VERSION in contexts supported by the relevant const rules. There is no computation parameter and no method body to dispatch.
An associated const belongs to the implementing type. It also makes a trait dyn-incompatible, so I consider whether trait objects are part of the design. A runtime method may be needed alongside or instead of the constant for object-dispatched code.
Use a concrete const fn when the type is known
An inherent function on a concrete type can be const:
impl Protocol {
const fn version() -> u32 { 3 }
}
This supports const evaluation without trait abstraction. It is a good fit when selection happens statically in source or generated code.
I can expose both an inherent const function and a normal trait method delegating to it. This duplicates a small surface but serves two distinct call contexts honestly.
Const evaluation is intentionally restricted
The const-evaluation Reference distinguishes const functions from ordinary functions and defines which expressions are accepted during compile-time evaluation.
Even a concrete const fn cannot perform every runtime operation. Allocation, destructors, mutable global state, I/O, and trait calls have rules that evolve with Rust. I test against the declared minimum Rust version rather than only the newest compiler.
Do not promise const accidentally
Once users depend on a public function in const contexts, removing its const capability can break source code. Internal implementation changes may also become constrained by what const evaluation permits.
I mark functions const when compile-time use is a real supported feature. I add a compile-time fixture such as a const VALUE: _ = Type::function() so CI verifies the promise directly.
I also test the declared MSRV. Const support for individual operations changes over Rust releases, so a function that is accepted as const by my current compiler may use an operation unavailable to downstream users on the minimum version. A runtime test cannot detect this compatibility failure; a pinned compiler check can.
Dynamic dispatch and const construction answer different questions
Trait methods abstract which implementation supplies behaviour. Constant evaluation asks whether a computation can run during compilation under a restricted machine.
Trying to combine them casually hides two selection times. If runtime plugin choice matters, a normal dyn-compatible method fits. If compile-time configuration matters, associated constants, const generics, or concrete const functions may fit.
My E0379 checklist
- Is the result fixed data that should be an associated const?
- Is runtime trait dispatch actually required?
- Can a concrete inherent const fn serve compile-time callers?
- Do trait objects need this API?
- What minimum Rust version must support the const operations?
- Is const use tested in an actual constant context?
- Would two explicit APIs be clearer than one overloaded promise?
The core principle is that const-callability and trait polymorphism are separate contracts. Stable Rust's ordinary trait methods do not combine them through const fn. I choose the representation according to when implementation selection happens and whether callers truly need compile-time evaluation.