Skip to content

← Generics and Traits step 17 of 24

Medium Primitives

impl Trait in argument position is not a generic

These two signatures accept exactly the same set of arguments:

pub fn generic<T: Debug>(t: T) -> String
pub fn apit(t: impl Debug) -> String

The second is argument-position impl Trait, usually written APIT. It reads beautifully — the parameter looks like an ordinary parameter, and there is no angle-bracket tax to pay before you have written your first line of generic code. That is why it is worth learning before <T: Trait>.

It is not, however, sugar. There is one difference and it matters at the boundary of any API you publish.

The difference

<T: Debug> declares a parameter with a name. Callers can supply it:

generic::<i64>(v)

impl Debug declares an anonymous parameter. There is no name, so there is nothing for a turbofish to point at:

apit::<String>(v.to_string())
error[E0107]: function takes 0 generic arguments but 1 generic argument
              was supplied
   |
   = note: `apit` has an anonymous type parameter that cannot be
           explicitly specified

The starter contains that exact call. Compile it, read E0107, then remove the turbofish. That is the whole lesson: if you publish an API using APIT and a caller ever needs to pin the type down, they cannot, and you will have to break the signature to let them.

The second difference follows from the first. Two impl Trait parameters are two independent anonymous types:

fn pair(a: impl Debug, b: impl Debug)          // a and b may differ
fn pair<T: Debug>(a: T, b: T)                  // a and b MUST be the same type

With <T> you can express “these two arguments have the same type”. With APIT you cannot, because there is no name to repeat.

Your task

pub fn generic<T: Debug>(t: T) -> String
pub fn apit(t: impl Debug) -> String
pub fn run(vals: Vec<i64>) -> Vec<String>

For each value, produce "{named}|{anon}" where named is generic::<i64>(v) — keep the turbofish, it is legal and it is half the demonstration — and anon is apit(v.to_string()).

Watch the expected output. For v = 1 the answer is:

1|"1"

The quotes are real. {:?} on an i64 prints 1; {:?} on a String prints "1", with quotes and with escaping, because Debug is for programmers and a programmer wants to see where the string ends. This is the single most useful practical difference between {} and {:?}.

When to use which

  • APIT for internal helpers and for parameters nobody will ever need to name — fn log(msg: impl Display). It is shorter and it reads better.
  • <T: Trait> the moment the type appears more than once, or the moment a caller might want to choose it. Public APIs default to this.

There is a clippy::impl_trait_in_params lint that flags APIT in public signatures, but it belongs to the restriction group — allow by default and never enabled wholesale — so it will not gate you here. Restriction lints are opinions, not rules; several of them contradict each other by design.

Remember the grade is compile + tests + clippy -D warnings.