Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

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.