Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-483 · Case file with fixtures · Case 455 of 694 · Compiler evidence

A Rust Trait Impl Cannot Invent an Associated Constant

Trait impl constants must fill constant slots declared by the trait. Add the constant only for a universal type-level capability, move implementation-specific data to an inherent impl, or pass runtime configuration explicitly.

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
An impl may select values only for constant slots promised by the trait; backend-specific metadata does not enter that contract automatically.
First discriminating check
Choose trait constant for universal type-level policy, inherent constant for concrete metadata, or an instance method for runtime-varying capacity.

I put const CAPACITY: usize = 64 inside impl Window for Buffer, but Window declared no such constant. Rust produced E0438 because a trait implementation may define only constants named by the trait.

The failing fixture is the constant counterpart of an extra method or associated type. The impl block supplies one known contract; it does not become a bag of type-level metadata.

Trait constants are part of generic capability

When a trait declares const CAPACITY: usize;, generic code bounded by that trait can use T::CAPACITY. Every implementation must provide a value unless the trait defines a default.

The associated-constant Reference defines declarations and definitions. The value has no separate storage location like a static item; it is inlined at use according to constant semantics.

Without the trait declaration, generic callers have no such guarantee.

Declare it when every implementation has meaningful capacity

The repaired fixture adds the constant to Window, then chooses 64 for Buffer.

This works when capacity is a type-level property stable for each implementation. Fixed block sizes, protocol widths, and compile-time limits can fit.

I define units in the name or documentation because an unqualified CAPACITY can mean bytes, items, or concurrent operations.

Use an inherent constant for concrete-only metadata

If only Buffer has this concept, I move it to:

impl Buffer {
    const CAPACITY: usize = 64;
}

Callers use Buffer::CAPACITY, and the Window abstraction remains smaller. This is often the correct repair when a backend optimisation does not affect generic correctness.

An inherent constant can coexist with trait constants, but qualified syntax may be needed to distinguish them.

Runtime configuration is not an associated constant

A buffer sized from environment configuration, negotiation, or constructor input should expose a method or field value, not a trait constant. One type can have many runtime instances with different capacities.

Forcing the value into the type may require const generics and create separate monomorphised types. That can be useful for fixed buffers but excessive for ordinary service configuration.

I ask whether the value varies by type, by instance, or by execution environment.

Defaults are public policy

A trait may provide const CAPACITY: usize = 64; as a default. Implementors inherit it unless they override the definition.

I use a default only when it is valid for all implementations, not merely common. A permissive default limit can hide an implementation that should make an explicit capacity decision.

Adding a default can ease evolution, but generic callers may begin relying on the value as part of behaviour.

Trait objects cannot expose associated constants directly

Traits with associated constants are not dyn-compatible under current Rust rules. If dynamic dispatch is required, a method such as fn capacity(&self) -> usize may better express the property.

This is an important design consequence of fixing E0438 by enlarging the trait. The trait Reference lists dyn-compatibility restrictions.

Static generic dispatch and dynamic object dispatch should be chosen deliberately.

Dependency and macro drift can create extras

A trait version may remove or rename a constant, while an old impl or derive still emits it. I compare the exact dependency declaration and enabled features before adding a new local constant.

For generated implementations, the trait schema should be versioned and tested as a compile fixture. Similar names across traits do not make associated items interchangeable.

My E0438 checklist

I also separate an advertised limit from current usage. CAPACITY might mean maximum supported elements, preallocated elements, or an invariant exact width. Generic algorithms behave differently for each. Before placing the constant in a trait, I name the semantic unit and test an implementation at the boundary. A method returning a runtime Capacity value may be safer when limits depend on platform resources. A type-level number is valuable only when callers can rely on it consistently.

  • Does the current trait declare this exact constant?
  • Is the value meaningful for every implementation?
  • Does it vary by type or by runtime instance?
  • Would an inherent constant keep the trait focused?
  • Are units and semantics part of the public contract?
  • Is a default valid for all future implementations?
  • Will adding the constant break trait-object use?
  • Did dependency or macro output drift across versions?

The core principle is that trait constants are shared type-level promises. I place a value in that contract only when every implementor can answer the same meaningful question; otherwise I keep it concrete or runtime-configured.