RFA-634 · Case file with fixtures · Case 606 of 694 · Compiler evidence
Const Parameter Types Cannot Depend on Another Generic Parameter
Const generics parameterise values of supported concrete types. Give capacity a stable type such as usize, and represent type-dependent values through traits or separate domain markers.
- 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
- Type selection and compile-time value selection were collapsed into one dependent parameter domain unsupported by Rust const generics.
- First discriminating check
- Choose a concrete const type such as usize and move type-dependent policy into an associated const, marker type, or runtime value.
Const generics let one type vary by a compile-time value, such as an array length or channel capacity. The value still needs one supported, concrete parameter type. The failing fixture declares const CAPACITY: T, making that type depend on a neighbouring generic T, and receives E0770.
Type selection and value selection are different axes
T asks the caller to choose a type. const CAPACITY: usize asks the caller to choose one usize value. If the const parameter’s own type were T, rustc would need to reason about which value domain and const identity rules apply after another substitution.
The official E0770 explanation repairs the declaration with a concrete usize. The Reference documents const generic parameters, their permitted types, and how const arguments may be used.
I separate “what elements are stored?” from “how many slots exist?” even when an external schema writes both in one phrase.
Capacity usually has a natural concrete type
The repaired fixture uses usize, matching array lengths, indexes, and memory capacities in Rust. Other domains may use u8 for a protocol tag or bool for a compile-time mode when supported and semantically correct.
I do not choose usize blindly for wire values. Its width changes by target, so an external field needs a fixed-width representation and validation before conversion to an in-memory size. The const parameter can remain usize after the boundary.
Units deserve names. Buffer<T, 16> does not say whether 16 means elements, bytes, shards, or retries. An alias, wrapper type, or associated constant can expose the domain vocabulary.
Type-dependent policy belongs in traits
If each element type has a preferred capacity, I can define a trait with an associated const, for example a policy implemented by each storage type. The container then refers to that policy in implementation logic instead of trying to make the const parameter’s type vary.
Associated constants and const parameters solve different problems. An associated constant is selected with an implementation; a const argument creates distinct type instantiations chosen by the caller. I decide who owns the choice.
A marker policy type can bundle several compile-time decisions and associated types. This sometimes produces clearer APIs than a long list of positional const values, though it may increase trait complexity and diagnostics.
Const values change type identity and code generation
Buffer<u8, 16> and Buffer<u8, 32> are different types. That can make invalid mixing impossible and help optimisation. It also creates monomorphisations, complicates collections of varying capacity, and can expose values in public signatures.
I use const generics when the value is a real compile-time invariant used by layout or algorithms. A runtime field is better when capacities vary frequently, come from configuration, or need one heterogeneous collection.
Public crates consider semver: changing a const parameter, its type, or its order can break annotations and trait implementations. Defaults may help some call sites but remain an interface decision.
Expressions and MSRV need verification
Stable Rust accepts many direct uses of const parameters, while more complex generic const expressions have evolved. I test the exact expression on the minimum supported Rust version instead of assuming a recent example works everywhere.
Compiler errors around unconstrained generic constants can appear after E0770 is fixed. I simplify the type-level arithmetic, add only justified bounds available on the project’s toolchain, or move calculation to runtime.
Compile-time computation is not automatically faster. It may increase build time and generated code. I measure runtime benefit, binary size, and compile cost for heavily parameterised libraries.
My E0770 checklist
- Does a const parameter’s declared type mention another generic parameter?
- What concrete value domain does the const actually represent?
- Is usize correct for memory shape, or is this an external fixed-width value?
- Should the caller choose the value, or should a trait implementation own it?
- Does the const need to be part of type identity at all?
- Are units and valid ranges visible in names and constructors?
- Does the expression work on the project’s pinned minimum Rust version?
- Have monomorphisation, build time, and binary-size effects been measured where relevant?
The core principle is that const generics vary a value inside a known compile-time domain. E0770 prevents that domain from depending on another unresolved type. I choose a concrete representation and move type-dependent policy into an explicit trait or runtime boundary.