RFA-528 · Case file with fixtures · Case 500 of 694 · Compiler evidence
An Empty Rust Generic Collection Needs Enough Type Context
Inference solves type variables from constraints; an unused empty collection contributes none for its element. Add the smallest annotation at the boundary that actually knows the intended type.
- 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
- An empty generic constructor creates an unconstrained T, and variable names, capacity, or human expectation do not supply a type-system equation.
- First discriminating check
- Find the unresolved parameter and add the smallest annotation at the binding, constructor, collection, or return boundary that actually owns the domain choice.
I wrote let items = Vec::new() and never inserted, returned, or otherwise constrained an element. Rust emitted E0282 because it could not infer which Vec<T> I intended.
The failing fixture has infinitely many valid candidates: Vec<u8>, Vec<String>, and every other sized element type can start empty.
Inference needs constraints, not intuition
Rust creates an unknown type variable for T and tries to solve it from assignments, arguments, method calls, returns, and annotations. An empty constructor contains no element whose type can constrain that variable.
The variable name items and nearby comments do not participate in type checking. The compiler refuses to pick an arbitrary default.
The official E0282 page demonstrates several annotation locations.
The repaired fixture annotates the owner
The repaired fixture writes let items: Vec<u8> = Vec::new(). This is clear because the binding owns the collection and its domain knows the element type.
let items = Vec::<u8>::new() would provide equivalent information at the constructor. Later items.push(1_u8) could also constrain the type.
I put the annotation where a reader first learns the domain decision.
More type text is not always better
For a long iterator chain, collect::<Vec<_>>() names the container while allowing the element to be inferred. A return annotation may remove the need for local turbofish syntax.
I add only enough information to select one solution. Repeating a huge concrete type at several points makes refactoring harder and can hide the one part that mattered.
The placeholder _ remains useful inside an otherwise constraining annotation.
Empty and None values often expose ambiguity
None, Ok(value), Err(error), and empty maps or sets leave one or more generic parameters unknown when surrounding context is absent.
I identify which parameter lacks evidence rather than annotating the entire expression blindly. A Result<_, Error> annotation may be enough when the success type is constrained later.
Compiler notes often show the unresolved type variable or bound.
Associated functions can hide an unused self type
The E0282 documentation also shows Foo::bar() where bar returns i32 but belongs to impl<T> Foo<T>. The return type gives no clue which Foo<T> owns the call.
Foo::<T>::bar() names that otherwise unused parameter. This is a different surface form of the same constraint problem: several instantiations produce an identical visible result.
I inspect both output and owning type parameters.
API design can provide better context
A function returning Vec<Event> gives callers a concrete type even when the result is empty. A builder field with a declared type similarly constrains Vec::new() at initialization.
If callers constantly need turbofish annotations, an API may expose too little type information or use a generic constructor whose choice has no semantic source. I consider a named concrete constructor or typed wrapper.
Numeric defaults do not solve collection element ambiguity
Unsuffixed integer literals can default under specific inference conditions, but an empty vector has no literal at all. Rust cannot assume i32 simply because it is a common numeric default; the eventual elements might be strings, events, or references.
Even Vec::with_capacity(10) leaves the element type unknown because capacity describes allocation count, not stored values. I add the element annotation and keep capacity as a separate performance choice.
Removing an unused collection may be the best repair
Sometimes E0282 points to scaffold code that creates a vector never used. Annotating it makes dead code compile without adding behaviour. I inspect whether the binding should exist at all. Deleting truly unused state reduces allocation and avoids inventing a type decision.
In a minimal evidence fixture the value exists to demonstrate inference. In production, the warning and surrounding change determine whether annotation or removal is honest.
My E0282 checklist
- Which generic parameter remains unknown?
- What assignments, arguments, pushes, or returns could constrain it?
- Does an empty or absent value provide no element evidence?
- Is the binding, constructor, collect call, or function return the clearest annotation point?
- Can
_leave already inferable pieces unstated? - Is an associated function's self type also ambiguous?
- Does the API repeatedly force callers to state internal types?
- Does the repaired test exercise the intended element type?
The core principle is that inference solves equations supplied by code. When an empty generic value gives no equation for one parameter, I state that domain choice once at the clearest boundary.