Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-499 · Case file with fixtures · Case 471 of 694 · Compiler evidence

A Rust Associated Constant Type Must Match Its Trait

An associated constant's declared type is part of the trait contract. An implementation supplies a value of that exact type; it cannot replace a boolean policy with an integer convention.

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 implementation chooses a value inside the trait's declared domain and cannot substitute an integer representation for the promised boolean type.
First discriminating check
Compare the exact declared and implemented types, convert and validate external representations at their boundary, and preserve the domain type in the trait impl.

I declared FeatureFlag::ENABLED as bool but implemented it as u8. Rust emitted E0326 because an associated constant implementation chooses a value, not a replacement type for the trait slot.

The failing fixture uses 1, which many systems treat as “enabled.” Rust does not apply that convention implicitly. A bool has values true and false; an integer has a wider domain and different operations.

The constant type belongs to the public contract

Generic code can rely on T::ENABLED being a boolean when T: FeatureFlag. It may use the value directly in an if, negate it with !, or expose it through another boolean API.

If one implementation supplied u8, every generic caller would need per-implementation conversion rules. That would defeat the purpose of the shared trait.

The official E0326 explanation states directly that associated constant types in an impl must match the trait definition.

The repaired implementation preserves the domain

The repaired fixture declares const ENABLED: bool = true and asserts the value. It uses the same item kind and exact type as the trait.

If the source value arrives as 0 or 1 from a protocol, I convert it at the boundary. I do not leak the wire representation into a boolean domain contract. The conversion should also decide how invalid values such as 2 are handled.

This keeps representation concerns outside generic policy code.

Rust does not perform truthy integer conversion

There is no implicit rule that zero is false and non-zero is true. This avoids ambiguity and catches data-domain mistakes early.

An explicit expression such as raw != 0 can produce a boolean at runtime. For a constant literal, I use true or false. If protocol semantics accept only zero and one, validation may be preferable to treating every non-zero value as enabled.

E0326 can therefore reveal missing boundary modelling, not just a typo after the colon.

Type aliases do not create a new representation

If the trait declares a type alias resolving to bool, an implementation still needs the compatible resolved type. A newtype such as struct Enabled(bool) is distinct and cannot replace bool without changing the trait.

Newtypes are useful when a domain needs validation, units, or separate implementations. But adopting one is an API change that callers should see. I do not expect aliasing or conversion traits to make mismatched associated constants identical automatically.

The compiler comparison keeps that boundary explicit.

Dependency upgrades can expose stale policy representations

A trait might change a constant from an integer code to a boolean, enum, or duration wrapper. Downstream impls then fail even if the constant name remains stable.

I read the changelog and resolved declaration before selecting a literal that merely compiles. A new enum may require handling more than yes/no; a duration may encode units that were previously implicit.

The correct repair preserves meaning across the migration, not only syntax.

Generated implementations should type-check their source model

A config generator may parse every literal as an integer and print that type into the impl. I prefer a schema where trait constant types are known and input values are validated before Rust generation.

Compile fixtures catch the final mismatch, while generator-level tests can give users a more local error such as “feature flag must be boolean.” Both layers are useful.

The Reference on associated constants provides the declaration and implementation forms the generator must preserve.

Numeric casts are not a contract repair

Writing 1 as bool is not available as a shortcut, and more generally a cast would not change the declared item type unless the final expression actually has the required type. Even when an explicit conversion exists, I document what information is lost.

For flags read from storage or FFI, I keep the raw integer in the boundary module and expose a validated boolean or enum to the trait. This gives malformed input one deliberate handling path and keeps generic users independent from transport details.

My E0326 checklist

  • What exact type does the trait declare for the constant?
  • What type did the implementation write or infer?
  • Is an external representation leaking into the domain type?
  • Does conversion need validation rather than a simple cast?
  • Did a dependency upgrade change units or valid values?
  • Is a type alias actually equivalent, or is this a distinct newtype?
  • Does generated configuration know the trait's expected type?
  • Does the repaired fixture use the constant as its promised type?

The core principle is that a trait constant fixes both a name and a value domain. Each implementation may choose a different value inside that domain, but it cannot choose a different type or an undocumented encoding convention.