Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

I read this code:

pub fn up_to(limit: u64) -> impl Generator<Yield = u64, Return = u64> {
    move || {
        for x in 0..limit {
             yield x;
        }
        return limit;
    }
}

What does impl mean? How might this be implemented in plain Rust or C++?

like image 278
Autodidact Avatar asked Jan 31 '18 14:01

Autodidact


People also ask

What does impl mean in Rust?

Keyword impl The impl keyword is primarily used to define implementations on types. Inherent implementations are standalone, while trait implementations are used to implement traits for types, or other traits. Functions and consts can both be defined in an implementation.

What is a trait in Rust?

A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.


2 Answers

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)?
like image 180
Matthieu M. Avatar answered Oct 20 '22 13:10

Matthieu M.


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
like image 33
user4815162342 Avatar answered Oct 20 '22 12:10

user4815162342