impl Trait for returning complex types with ease

Minimum Rust version: 1.26

impl Trait is the new way to specify unnamed but concrete types thatimplement a specific trait. There are two places you can put it: argumentposition, and return position.

  1. trait Trait {}
  2. // argument position
  3. fn foo(arg: impl Trait) {
  4. }
  5. // return position
  6. fn foo() -> impl Trait {
  7. }

Argument Position

In argument position, this feature is quite simple. These two forms arealmost the same:

  1. trait Trait {}
  2. fn foo<T: Trait>(arg: T) {
  3. }
  4. fn foo(arg: impl Trait) {
  5. }

That is, it's a slightly shorter syntax for a generic type parameter. Itmeans, "arg is an argument that takes any type that implements the Traittrait."

However, there's also an important technical difference between T: Traitand impl Trait here. When you write the former, you can specify the type ofT at the call site with turbo-fish syntax as with foo::<usize>(1). In thecase of impl Trait, if it is used anywhere in the function definition, thenyou can't use turbo-fish at all. Therefore, you should be mindful thatchanging both from and to impl Trait can constitute a breaking change forthe users of your code.

Return Position

In return position, this feature is more interesting. It means "I amreturning some type that implements the Trait trait, but I'm not going totell you exactly what the type is." Before impl Trait, you could do thiswith trait objects:

  1. #![allow(unused_variables)]
  2. fn main() {
  3. trait Trait {}
  4. impl Trait for i32 {}
  5. fn returns_a_trait_object() -> Box<dyn Trait> {
  6. Box::new(5)
  7. }
  8. }

However, this has some overhead: the Box<T> means that there's a heapallocation here, and this will use dynamic dispatch. See the dyn Traitsection for an explanation of this syntax. But we only ever return onepossible thing here, the Box<i32>. This means that we're paying for dynamicdispatch, even though we don't use it!

With impl Trait, the code above could be written like this:

  1. #![allow(unused_variables)]
  2. fn main() {
  3. trait Trait {}
  4. impl Trait for i32 {}
  5. fn returns_a_trait_object() -> impl Trait {
  6. 5
  7. }
  8. }

Here, we have no Box<T>, no trait object, and no dynamic dispatch. But westill can obscure the i32 return type.

With i32, this isn't super useful. But there's one major place in Rustwhere this is much more useful: closures.

impl Trait and closures

If you need to catch up on closures, check out their chapter in thebook.

In Rust, closures have a unique, un-writable type. They do implement the Fnfamily of traits, however. This means that previously, the only way to returna closure from a function was to use a trait object:

  1. #![allow(unused_variables)]
  2. fn main() {
  3. fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
  4. Box::new(|x| x + 1)
  5. }
  6. }

You couldn't write the type of the closure, only use the Fn trait. That meansthat the trait object is necessary. However, with impl Trait:

  1. #![allow(unused_variables)]
  2. fn main() {
  3. fn returns_closure() -> impl Fn(i32) -> i32 {
  4. |x| x + 1
  5. }
  6. }

We can now return closures by value, just like any other type!

More details

The above is all you need to know to get going with impl Trait, but forsome more nitty-gritty details: type parameters and impl Trait workslightly differently when they're in argument position versus returnposition. Consider this function:

  1. fn foo<T: Trait>(x: T) {

When you call it, you set the type, T. "you" being the caller here. Thissignature says "I accept any type that implements Trait." ("any type" ==universal in the jargon)

This version:

  1. fn foo<T: Trait>() -> T {

is similar, but also different. You, the caller, provide the type you want,T, and then the function returns it. You can see this in Rust today withthings like parse or collect:

  1. let x: i32 = "5".parse()?;
  2. let x: u64 = "5".parse()?;

Here, .parse has this signature:

  1. pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err> where
  2. F: FromStr,

Same general idea, though with a result type and FromStr has an associatedtype… anyway, you can see how F is in the return position here. So youhave the ability to choose.

With impl Trait, you're saying "hey, some type exists that implements thistrait, but I'm not gonna tell you what it is." So now, the caller can'tchoose, and the function itself gets to choose. If we tried to define parsewith Result<impl F,… as the return type, it wouldn't work.

Using impl Trait in more places

As previously mentioned, as a start, you will only be able to use impl Traitas the argument or return type of a free or inherent function. However,impl Trait can't be used inside implementations of traits, nor can it beused as the type of a let binding or inside a type alias. Some of theserestrictions will eventually be lifted. For more information, see thetracking issue on impl Trait.