RFA-627 · Case file with fixtures · Case 599 of 694 · Compiler evidence
Generic Arguments Must Follow Parameter Kind Order
Lifetime, type, and const arguments are positional parts of one signature. Read the declaration or generated docs, preserve the order, and use aliases to name recurring combinations.
- 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
- Lifetime and type arguments were reordered as if angle-bracket inputs were named rather than one positional multi-kind signature.
- First discriminating check
- Open the declaration or generated docs, align each lifetime, type, and const argument, then verify its semantic meaning.
Rust generics can mix lifetimes, types, and const values. Angle brackets do not label each argument; position and syntax connect it to the declaration. The failing fixture supplies () before 'static to a type declared as Borrowed<'a, T>, and rustc reports E0747.
Start from the declaration, not the use site
The official E0747 explanation fixes the arguments by matching the parameter order. The Reference’s generic parameter rules say lifetime parameters come before type and const parameters in declarations, while type and const parameters may be intermingled.
I open the actual type declaration or generated rustdoc. Guessing from familiar names is unreliable, especially for nested aliases and macro-generated types. The error often highlights the first mismatched kind, which gives a precise place to align the two lists.
Lifetime arguments begin with an apostrophe, const arguments are value expressions in allowed positions, and type arguments name types. These shapes help, but inference and braces around const expressions can make a complex path less obvious.
The repair preserves semantic roles
The repaired fixture writes Borrowed<'static, ()>. This makes the first argument a lifetime and the second a type, exactly as declared.
I still ask whether 'static is truthful. It is correct for the fixture’s actual static value. It would be a harmful cargo-cult repair for data borrowed from a request or stack frame. Correct ordering does not validate the meaning of each argument.
Similarly, changing a const argument to a type-shaped wrapper merely to satisfy syntax can hide the intended invariant. I name dimensions, capacities, or protocol versions in aliases when their positions are hard to remember.
Aliases reduce repeated positional complexity
A public type with several parameters may be flexible but noisy at common use sites. A type alias such as type RequestView<'a> = View<'a, Request, 16> can give each recurring composition a domain name.
The alias should clarify, not conceal important choices. If callers need to reason about capacity for correctness, the name or documentation must preserve it. An alias is not a new type and cannot enforce additional invariants by itself.
Builder methods and constructors can also let inference fill types from values. I prefer inference when local evidence is clear, but I annotate boundaries, empty collections, and None values where no expression constrains the argument.
Refactors can make old paths misleading
Adding or reordering generic parameters is disruptive. Defaults can reduce source edits for trailing type or const parameters in permitted contexts, but they do not make every position named. Public crates should treat parameter order as an API design choice.
Macros may copy parameter declarations and argument applications in different code paths. A new const parameter inserted in only one list can surface as E0747 far from the macro invocation. I inspect expanded code and generate both lists from one structured representation.
Higher-ranked lifetimes, associated types, and qualified paths can make diagnostics dense. I simplify a failing alias into one direct path, align kinds, then rebuild the larger expression. This is faster than moving punctuation randomly.
The Reference section on paths in types documents generic argument syntax, including ambiguity and qualified forms.
Turbofish does not change the signature
Using ::<...> on functions or methods can make arguments explicit, but it does not permit a different order. Lifetimes are often inferred at function call sites, while type and const values may be supplied. The declaration remains the source of truth.
Compiler suggestions are useful, but I review the resulting lifetime and const semantics. “It compiles” proves correspondence, not that a buffer length, borrow duration, or policy type matches the domain.
Tests should exercise aliases and constructors shown in documentation. For generated APIs, compile tests are cheap protection against declaration/application drift.
My E0747 checklist
- What are the generic parameters in their exact declared order?
- Which supplied argument has a different kind at the highlighted position?
- Is the chosen lifetime real, especially if it is
'static? - Does each const value represent the intended invariant and unit?
- Could inference remove noise without creating ambiguity?
- Would a domain-named alias make a recurring combination understandable?
- Did a public refactor or macro update only one of two parameter lists?
- Do rustdoc and compile tests show the supported application syntax?
The core principle is that generic arguments form a positional contract across several kinds. E0747 catches a structural mismatch. I align the use with the declaration, then verify that every lifetime, type, and value still expresses the intended ownership and domain rule.