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++?
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.
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.
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:
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With