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 types

Fix 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 Trait for one-off flexible parameters such as impl AsRef<Path> or impl 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 Trait or Box<dyn Trait> only when runtime heterogeneity or code-size tradeoffs matter.
  • ✅ Move complex bounds into a where clause 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 Trait parameters 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 Trait to 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 Trait from 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