Accepting impl Trait vs Generics
Use argument-position impl Trait for simple anonymous bounds, and named generics when a type relationship must be expressed more than once.
What it is
Rust gives two common spellings for generic function parameters:
fn f(x: impl Trait) and fn f<T: Trait>(x: T).
Both use static dispatch and monomorphization for ordinary trait bounds.
The difference is API shape.
impl Trait is concise when the concrete type is irrelevant and appears once.
Named generic parameters are necessary when multiple arguments must have the same type, when the return type depends on the input type, or when bounds get complex.
How it works
Argument-position impl Trait is syntax for an anonymous generic parameter.
Each impl Trait parameter is independently chosen by the caller.
If two parameters must be the same type, name the generic.
For public APIs, choose the spelling that best communicates relationships.
Do not confuse this decision with dyn Trait; trait objects are a separate dynamic-dispatch design covered by Dyn Compatibility.
Both fn f(x: impl Trait) and fn f<T: Trait>(x: T) are statically dispatched by default.
The compiler generates specialized code for the concrete types used at call sites.
That is why impl Trait in argument position is an API readability choice, not a runtime-polymorphism switch.
Named generics become necessary as soon as the API has a relationship to express: same input type, associated type constraints reused in multiple places, return type tied to input type, or a where clause that would be unreadable inline.
If the function body needs to name the type, the signature probably should too.
Example
fn sum_any(values: impl IntoIterator<Item = i32>) -> i32 {
values.into_iter().sum()
}
fn choose_same<T: Ord>(left: T, right: T) -> T {
if left >= right { left } else { right }
}
fn main() {
assert_eq!(sum_any([1, 2, 3]), 6);
assert_eq!(sum_any(vec![4, 5]), 9);
assert_eq!(choose_same("b", "a"), "b");
}Relationship example
Two anonymous impl Trait parameters do not mean “same type.”
Use a named generic when sameness is part of the contract.
use std::fmt::Display;
fn pair_any(left: impl Display, right: impl Display) -> String {
format!("{left}:{right}")
}
fn pair_same<T: Display>(left: T, right: T) -> String {
format!("{left}:{right}")
}
fn main() {
assert_eq!(pair_any("id", 42), "id:42");
assert_eq!(pair_same("left", "right"), "left:right");
}Common errors
Using two impl Trait parameters and assuming they are the same can produce mismatched-type errors inside the function or at later refactors:
error[E0308]: mismatched typesFix the signature to name the generic relationship, such as fn merge<T: Read>(left: T, right: T).
If callers should be allowed to pass different types, keep separate impl Trait parameters and avoid operations that require sameness.
Best practice
- ✅ Use
impl Traitfor one-off flexible parameters such asimpl AsRef<Path>orimpl IntoIterator. - ✅ Use named generics when two parameters share a type or when the type appears in the return value.
- ✅ Keep generic bounds as narrow as possible; ask for behavior, not a concrete container.
- ✅ Consider
&dyn TraitorBox<dyn Trait>only when runtime heterogeneity or code-size tradeoffs matter. - ✅ Move complex bounds into a
whereclause before the signature becomes hard to scan. - ✅ Use associated type bounds such as
IntoIterator<Item = Event>when the item relationship is the real API.
Pitfalls
- ⚠️ Do not write two
impl Traitparameters when they must be the same concrete type. - ⚠️ Avoid over-generic APIs that make error messages and compile times worse without real flexibility.
- ⚠️ Do not use
impl Traitto hide ownership choices; callers still need to know whether values are borrowed, moved, or stored. - ⚠️ Do not accept
impl Into<T>if conversion failure is possible; use TryFrom and TryInto or a named checked constructor. - ⚠️ Do not return
impl Traitfrom public APIs casually if callers may need the concrete type’s additional methods.
See also
AsRef for Flexible Arguments · From and Into · Conversion Traits · Dyn Compatibility · Zero-Cost Abstractions · Builder Pattern · Generic Parameters · The Iterator Trait · Idioms & API Design
Sources
- The Rust Programming Language, “Generic Data Types” - the-book, https://doc.rust-lang.org/book/ch10-01-syntax.html
- The Rust Reference, “
impl Trait” - the-reference, https://doc.rust-lang.org/reference/types/impl-trait.html - The Rust Reference, “Generic parameters” - the-reference, https://doc.rust-lang.org/reference/items/generics.html
