What does `impl` mean when used as the argument type or return type of a function?

This is the new impl Trait syntax which allows the programmer to avoid naming generic types. The feature is available as of Rust 1.26.

Here, it is used in return position to say "the type returned will implement this trait, and that's all I'm telling you". In this case, note that all return paths of the function must return the exact same concrete type.

The syntax can also be used in argument position, in which case the caller decides what concrete type to pass.

See also:

  • Using impl Trait in Trait definition
  • What are the differences between an impl trait argument and generic function parameter?
  • What makes `impl Trait` as an argument "universal" and as a return value "existential"?
  • What is the correct way to return an Iterator (or any other trait)?

What does impl mean?

As Matthieu explained, impl X means "return a concrete implementation of trait X". Without this you have the choice of returning a concrete type that implements the trait, e.g. an UpToImpl, or erasing the type by returning a Box<Generator>. The former requires exposing the type, while the latter carries the run-time cost of dynamic allocation and virtual dispatch. More importantly, and this is the clincher for the generator case, the former approach precludes returning a closure, because closures return values of anonymous types.

How might this be implemented in plain Rust or C++?

If you mean how to implement up_to, and you want to do it without incurring allocation overhead, you have to give up using a closure and manually rewrite the generator into a state machine that implements the Generator trait:

#![feature(generator_trait)]
use std::{
    ops::{Generator, GeneratorState},
    pin::Pin,
};

pub struct UpToImpl {
    limit: u64,
    x: u64,
}

impl Generator for UpToImpl {
    type Yield = u64;
    type Return = u64;

    fn resume(mut self: Pin<&mut Self>) -> GeneratorState<u64, u64> {
        let x = self.x;
        if x < self.limit {
            self.x += 1;
            GeneratorState::Yielded(x)
        } else {
            GeneratorState::Complete(self.limit)
        }
    }
}

pub fn up_to(limit: u64) -> UpToImpl {
    UpToImpl { x: 0, limit }
}

fn main() {
    let mut v = Box::pin(up_to(3));
    println!("{:?}", v.as_mut().resume());
    println!("{:?}", v.as_mut().resume());
    println!("{:?}", v.as_mut().resume());
    println!("{:?}", v.as_mut().resume());
}

This kind of transformation is essentially what the Rust compiler does behind the scenes when given a closure that contains yield, except that the generated type equivalent to UpToImpl is anonymous. (A similar but much simpler transformation is used to convert ordinary closures to values of anonymous types that implement one of the Fn traits.)

There is another difference between returning impl Generator and a concrete type. When returning UpToImpl, that type has to be public, and thus becomes part of the function signature. For example, a caller is allowed to do this:

let x: UpToImpl = up_to(10);

That code will break if UpToImpl is ever renamed, or if you decide to switch to using a generator closure.

The up_to in this answer compiles even when changed to return impl Generator, so once impl trait is made stable, that will be a better option for its return type. In that case, the caller cannot rely or refer to the exact return type, and it can be switched between any type that implements the trait, including the anonymous closure, without loss of source-level backward compatibility.

See also:

  • Lazy sequence generation in Rust

Tags:

Rust