Every `impl Trait` argument is its own anonymous type parameter

Today I Learned · August 21, 2026

Every impl Trait argument is its own anonymous type parameter

From a Rustlings traits exercise: two impl Trait arguments look like they share one bound, but each occurrence is a separate generic parameter.

fn compare_license_types(
    software1: impl Licensed,
    software2: impl Licensed,
) -> bool

is conceptually:

fn compare_license_types<T: Licensed, U: Licensed>(
    software1: T,
    software2: U,
) -> bool

So compare_license_types(SomeSoftware, OtherSoftware) compiles fine — T and U resolve independently, and the function compares them through the shared Licensed bound.

The trap: one named parameter

fn compare_license_types<T: Licensed>(software1: T, software2: T) -> bool

One parameter means one concrete type for both arguments:

error: expected `SomeSoftware`, found `OtherSoftware`

Same call site, completely different contract.

And Box<dyn Licensed> is a third thing

fn compare_license_types(
    software1: Box<dyn Licensed>,
    software2: Box<dyn Licensed>,
) -> bool

Trait objects can hold different concrete types at runtime — but the caller must construct them explicitly:

compare_license_types(Box::new(SomeSoftware), Box::new(OtherSoftware))

Rust never implicitly heap-allocates to coerce a value into a trait object, so this is not a drop-in signature swap when the call sites are fixed.

Side by side

Signature Args can differ? Dispatch
a: impl Licensed, b: impl Licensed Yes — two anonymous params static (monomorphized)
<T: Licensed>(a: T, b: T) No — one param fixes one type static
a: Box<dyn Licensed>, b: Box<dyn Licensed> Yes — erased at runtime dynamic (vtable)

The takeaway

impl Trait does not mean trait object. x: impl Licensed says “there is a concrete type here that implements Licensed; compiler, figure out which.” x: Box<dyn Licensed> says “forget the concrete type entirely; hand me an erased object.” Different caller-side representation, different dispatch semantics — which is why Box<dyn Licensed> isn’t a substitute for impl Licensed.

Gotchas