Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-510 · Case file with fixtures · Case 482 of 694 · Compiler evidence

Non-Generic Rust Types Cannot Receive Generic Arguments

Generic arguments instantiate declared parameters; they cannot attach metadata to a non-generic type. Use a wrapper when another type must become part of domain identity.

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
Generic angle brackets fill slots from one resolved declaration and cannot attach units, conversion sources, or other metadata to an already concrete builtin type.
First discriminating check
Resolve the path and identify what the extra argument meant, then remove it, move it to the proper conversion, or introduce a real generic wrapper.

I wrote u64<u8> as though the integer could receive another type. Rust emitted E0109 because u64 declares no generic parameter.

The failing fixture is small, but the same error appears through aliases and paths where it is less clear which segment owns the angle brackets.

Generic arguments fill declared slots

An item such as Vec<T> declares T, so Vec<u8> selects a concrete element type. Builtin u64 is already one concrete numeric type and exposes no open slot.

The official E0109 page describes arguments supplied to a type that does not need them. Angle brackets do not decorate an arbitrary type with extra meaning.

I always locate the declaration and count its lifetime, type, and const parameters.

The repaired fixture removes unsupported syntax

The repaired fixture keeps type Count = u64 and constructs a numeric value. This preserves alias semantics: Count remains exactly the builtin integer.

If u8 was intended as metadata, deleting it may lose the design. I first ask what it represented: unit, encoding, marker, or storage element. A dedicated generic wrapper may be the honest model.

Compilation tells me the syntax is invalid, not why the extra argument was written.

A wrapper can carry type-level meaning

struct Count<Unit> { raw: u64, marker: PhantomData<Unit> } can distinguish requests from bytes or seconds at compile time. The wrapper declares a real generic parameter and decides whether it affects layout or only type identity.

This adds conversions and trait implementations. I use it when preventing unit confusion is worth that cost. A plain alias is better when the name is documentation and free interchangeability is desired.

The parameter must serve a real invariant rather than decorate syntax.

Put arguments on the correct path segment

Qualified paths can contain several names. A module is not generic, while a type or function inside it may be. Enum variant constructor arguments can also appear at a different segment than expected.

The E0109 documentation mentions Option::None::<u32> as a constructor-oriented form. I inspect what the turbofish applies to instead of moving brackets by trial and error.

IDE go-to-definition is useful when re-exports hide the owning declaration.

Aliases can hide non-generic targets

An alias may be non-generic even when the underlying implementation type is generic because the alias fixes every parameter. Users instantiate the alias according to its own declaration.

Conversely, a generic alias can expose selected parameters from a longer underlying type. The public alias is the contract at the use site. Copying the underlying type's argument list onto it can create E0109 or E0107.

I read the nearest resolved alias before expanding internals.

Version drift can change the visible API

A dependency may replace a generic wrapper with a concrete alias or move a parameter to a method. Examples for another version then put angle brackets on a now non-generic name.

I confirm the resolved crate version and enabled features. The fix may involve updating the example, selecting another exported type, or following a migration that moved configuration from compile time to runtime.

Removing syntax without understanding the change can compile with wrong defaults.

Separate type arguments from conversion targets

Sometimes the intended code was a conversion, not generic instantiation. value as u64, u64::from(value), and value.parse::<u64>() put type information in three different roles. u64<u8> does none of them.

I choose conversion syntax according to safety and failure behaviour. From is infallible, TryFrom can validate, parsing can fail on text, and as follows its defined cast rules. Moving u8 to the right place is not enough unless I know whether it describes the input, output, or validation boundary. This distinction makes the repair useful beyond one parser error.

My E0109 checklist

  • What exact item does the path resolve to?
  • Does that item declare any generic parameters?
  • Is an alias fixing parameters of an underlying generic type?
  • What semantic role was the extra argument meant to carry?
  • Would a real generic wrapper enforce a useful invariant?
  • Do the angle brackets belong on a constructor or method instead?
  • Does the dependency version match the example?
  • Does the repaired code preserve the intended domain distinction?

The core principle is that generic syntax instantiates a parameter promised by a declaration. If no slot exists, I remove the argument or introduce an abstraction that truly owns its meaning.