RFA-522 · Case file with fixtures · Case 494 of 694 · Compiler evidence
Rust Generic Types Use Angle Brackets, Not Parentheses
Parentheses describe calls and Fn-family argument notation, while ordinary type parameters use angle brackets. Read the syntactic context before deciding whether a path is a type or constructor.
- 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
- Parentheses normally form calls or tuples and have special Fn-family notation, whereas Vec declares an ordinary type parameter instantiated with angle brackets.
- First discriminating check
- Determine whether the source position describes a type, tuple, call, or Fn signature, then rebuild the syntax tree with each delimiter serving that role.
I wrote Vec(&str) in a type annotation. Rust emitted E0214 because ordinary generic type arguments use angle brackets: Vec<&str>.
The failing fixture is easy to recognise in isolation. It becomes more confusing when a tuple type, function trait, or constructor call appears nearby.
Type application and value calls use different delimiters
Vec<&str> instantiates a generic type parameter. Vec::new() calls an associated function. Some(value) calls a tuple-like enum variant constructor.
Angle brackets therefore belong to type and generic argument structure, while parentheses normally hold value arguments to a callable expression.
The official E0214 page gives the direct correction.
The repaired fixture gives Vec its element type
The repaired fixture writes Vec<&str>, constructs the vector with vec!, and checks its length.
The &str appears in type position and describes every element. The string literal appears in value position and becomes one element at runtime.
Keeping those levels separate makes nested generic syntax easier to read.
Tuple types legitimately use parentheses
Vec<(&str, u32)> is a vector whose one type argument is a tuple type. Both delimiter pairs are needed: angle brackets for Vec and parentheses for the tuple's fields.
Writing Vec(&str, u32) does not mean the same thing. I sketch the type tree from outside in: vector of tuple of string slice and integer.
This habit prevents delimiter guessing in deeply nested types.
Fn-family notation is the special exception
Bounds such as Fn(&str) -> usize use parenthesised argument notation because they describe a callable signature. The E0214 documentation calls out this special family.
This does not generalise to Vec, Option, or user structs. Even a user trait that models a callable usually uses ordinary angle-bracket generics unless it is one of the language-supported Fn traits.
Syntax follows the declaration and language grammar, not conceptual similarity.
Type aliases can improve deeply nested readability
When the fixed type becomes Vec<Result<(&str, u32), Error>>, a meaningful alias can expose the domain shape in pieces. I avoid aliases that hide important ownership, especially borrowed lifetimes.
Formatting each nested layer across lines can also be enough. The goal is to make delimiters show structure, not to compress the type into one dense line.
Compiler errors often become clearer after naming one intermediate type.
Macro fragments must use the correct syntactic category
A macro capturing an expression fragment cannot always be reused where a type fragment is expected. Generated parentheses may turn intended generic arguments into call-like syntax.
I declare macro inputs with appropriate fragment kinds and compile examples containing reference, tuple, and nested generic types. Inspecting expansion reveals whether the wrong delimiter comes from the invocation or generator.
Constructor names can look like types
Tuple structs and tuple-like enum variants create values with parentheses, so Wrapper(value) is valid even though Wrapper<T> names the type. The same path can participate in different namespaces and contexts. I ask whether I am describing a value to create or a type that another value has.
For Option::<u8>::Some(1), the generic argument belongs to the enum type while parentheses pass the payload to the variant constructor. In inferred code, Some(1_u8) is enough. Keeping constructor calls and generic instantiation visually distinct reduces syntax errors and also clarifies which choices happen at compile time.
Formatting is part of debugging
I run the formatter after the parser accepts a repair. Rustfmt makes nested angle brackets, tuple parentheses, and reference types consistent, which helps reveal whether the final structure matches the intended type tree. Formatting cannot fix E0214 itself because invalid syntax never reaches that stage.
When teaching this distinction, I keep one value example beside one type example. Seeing Vec::<u8>::new() together shows that angle brackets select the element type and parentheses invoke the constructor. The two operations can occur in one expression without competing for the same role.
My E0214 checklist
- Is this source position expecting a type or a value expression?
- Does the path name an ordinary generic type?
- Should its arguments use angle brackets?
- Is a parenthesised inner type actually a tuple?
- Am I looking at the special
Fn(Args) -> Outputnotation? - Would an alias make nested structure clearer?
- Did a macro confuse type and expression fragments?
- Does the repaired declaration match the item's parameter list?
The core principle is that delimiters expose the syntax tree. Angle brackets instantiate ordinary generics; parentheses form calls, tuples, and the special Fn-family signature notation.