impl Trait

I’m a little embarrassed that I’ve been writing Rust for the last four years and just now realized that fn foo(arg: impl Trait) is not a function accepting a trait object but is in fact exactly the same as fn foo<T: Trait>(arg: T). 1 It must have been a recent change, right? Oh, 2018. Anyways, why was this syntax added? I don’t think that this syntax is enough of an improvement 2 to add the complexity of a second way of doing things.

It turns out that there were a lot of reasons, 3 but one of the main reasons is symmetry with impl Trait in the return position, i.e. fn foo() -> impl Trait. This is more interesting because it is not equivalent to fn foo<T: Trait>() -> T. In fact, it is the complete opposite: a function returning an impl Trait can only return a single concrete type.

In type theory this is an existential type, where the caller only knows that a type exists that satisfies the given abstract type. 4 In Rust, this allows the compiler to use the returned type as if it was a concrete type (e.g. static dispatch, no boxing) while hiding that from the caller. The impl Trait in the argument position is a universal type, where the callee must accept all types that satisfy the abstract type.

Are there any real-world uses for impl Trait as a return type? There is one very prominent (but not obvious) example—async functions actually return an impl Future<Output=T>. Because futures have unique, un-writable types, they can only be returned either as impl Future<Output=T> or as Box<dyn Future<Output=T>>, and so the impl Trait feature allows Rust to have stack-allocated futures.

Anyways, I doubt I will be writing functions that return an impl Trait very often, but it’s a really neat example of how Rust enables zero-cost abstractions.

  1. I blame the similarity to dyn Trait. fn foo<T: Trait>(arg: T) is so obviously generic while fn foo(arg: impl Trait) (generic) looks very similar to fn foo(arg: Box<dyn Trait>) (not generic). I think I just saw Trait in the argument position and assumed it was the latter.

  2. Actually, I much prefer the more verbose syntax. It feels much more intentional.

  3. See RFC 1951 for a more in-depth discussion.

  4. In Rust, a function that returns an existential type can only return a single concrete type, but that is not a general fact about existential types.