RFA-430 · Case file with fixtures · Case 402 of 694 · Compiler evidence
A Const Parameter Type Cannot Depend on Another Generic Parameter
Stable const parameters need an independently known concrete parameter type. Use usize or another supported concrete scalar, and model relationships to T through fields, traits, associated constants, or validated constructors.
- 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
- Stable const parameters require an independently known concrete type so rustc can validate their values and use them as part of type identity.
- First discriminating check
- Give N a supported concrete type such as usize, then model any relationship to T through the trait, fields, associated constants, or constructors.
I wanted one generic type to choose both the stored value type and the type of a compile-time constant. I wrote Buffer<T, const N: T>. Rust rejected the declaration with E0770.
The failing fixture is only a structure declaration. No use site is needed: the parameter list itself asks the type of N to depend on T.
Type parameters and const parameters have different roles
T selects a type. N selects a value known as a const argument. Although both appear between angle brackets, the compiler needs a valid concrete type for each const parameter before it can reason about allowed values and type identity.
The E0770 explanation states the direct rule: the type of a const parameter must not reference another generic parameter.
This is invalid:
struct Buffer<T, const N: T> { value: T }
Changing parameter order does not help because the dependency remains.
Array lengths usually want usize
The repaired fixture declares const N: usize. This fits the common use where N is a count or array length:
struct Buffer<T, const N: usize> {
value: T,
}
A realistic buffer would likely contain [T; N]. Rust array length is a usize const expression, so the concrete type expresses the operation rather than the element representation.
I separate “what is stored?” from “how many are stored?” even when a domain file encodes both with the same integer width.
Const generics participate in type identity
Buffer<u8, 4> and Buffer<u8, 8> are different types. This can enforce dimensions, packet widths, matrix sizes, or fixed-capacity policies at compile time.
It also means runtime configuration cannot be inserted directly as N. A value read from a file or network is not a const generic. I validate it at runtime and choose a dynamic representation, or dispatch among a small known set of const instantiations.
Const generics are not a way to turn every integer into compile-time state automatically.
Use an associated constant when the type chooses a value
Sometimes the intended relationship is “each type defines its own width.” A trait can express that:
trait Format {
const WIDTH: usize;
}
Generic code can read T::WIDTH. Whether it can use that value in every const expression depends on the language rules for the particular position, so I compile the intended shape rather than assuming any associated constant is accepted anywhere.
This design gives one width per implementation. An independent const N: usize allows many widths for the same T. That cardinality decides which model is honest.
Store a runtime value when the choice is dynamic
If the width arrives from configuration, a field such as width: usize may be the correct answer. The constructor can validate bounds, and methods can return Result for operations that depend on them.
Moving a runtime policy into a const parameter can explode monomorphised code and complicate APIs without improving safety. I reserve type-level values for properties that callers really know statically and that prevent meaningful invalid combinations.
A newtype does not bypass the rule
Defining struct Count(u16) and asking for const N: T still depends on T. Defining const N: Count also depends on whether that concrete type is permitted for const parameters under the current stable language rules.
The const-generics Reference lists supported const parameter types and where const arguments may appear. I use that list rather than extrapolating from ordinary const items.
Conversion belongs at a boundary
If a protocol width is u16 but an array length is usize, I convert with range validation when building the runtime or generated representation. Using usize as the const parameter does not mean the wire format changed; it means Rust's count operation has a clear type.
I test maximums and target differences when converting to usize, especially if the code supports platforms with different pointer widths.
My design questions
- Is the constant a count, flag, character, or another supported concrete kind?
- Is it known at compile time or only at runtime?
- Can one T have several N values?
- Would one associated constant per T express the domain better?
- Does the const value prevent invalid states worth encoding in the type?
- Will many instantiations increase compile time or code size?
- Where should numeric conversion and validation occur?
The core principle is that const generics add values to type identity, but those values still need independently defined types. I give the const parameter a concrete type, then model its relationship with T through the abstraction instead of trying to make one generic parameter define the kind of another.