Crate fauxgen

source ·
Expand description

This crate implements generators for Rust.

Rust has built-in generators but they are currently unstable and so they can only be used on nightly. This crate allows you to write your own generators on stable rust by using async-await underneath the hood.

Defining a Generator

This crate provides two different ways to define generators. The first is as a top-level function:

#[fauxgen::generator(yield = i32)]
fn generator() {
    r#yield!(1);
    r#yield!(2);
}

and the second is as a lambda using the gen! macro:

use fauxgen::{gen, GeneratorToken};

let generator = fauxgen::gen!(|token: GeneratorToken<_>| {
    token.yield_(1i32).await;
    token.yield_(2i32).await;
});

In this case the generator uses a GeneratorToken instead of the yield! macro.

Generators can also be async

use std::time::Duration;

#[fauxgen::generator(yield = u32)]
async fn generator() {
    for i in 0u32..10 {
        tokio::time::sleep(Duration::from_millis(50)).await;
        r#yield!(i * 2);
    }
}

Using a Generator

Generators all implement either Generator or AsyncGenerator. Simple generators implement either Iterator or Stream, depending on whether the generator is async. This means that you can easily combine generators.

Here we implement a generator that returns all the powers of two for a u32:

#[fauxgen::generator(yield = u32)]
fn range(max: u32) {
    for i in 0..max {
        r#yield!(i);
    }
}

#[fauxgen::generator(yield = u32)]
fn powers_of_two() {
    for i in std::pin::pin!(range(31)) {
        r#yield!(1 << i);
    }
}

Note that because fauxgen generators are actually rust futures under the hood you will need to pin them before you can use them.

More Advanced Generator Usage

Most use cases for generators will likely involve using them as iterators or streams. However, that is not all that they can do. In addition to the yield parameter, generators have both

  • an argument: which is the value passed in to resume, and,
  • a return value: which is the value returned when the generator completes.

A complete generator that uses all of these looks like this:

use fauxgen::{GeneratorState, Generator};

#[fauxgen::generator(yield = String, arg = u32)]
fn format_each() -> u64 {
    let mut count = 0;
    let mut value = 0;

    while value < 100 {
        value = r#yield!(value.to_string());
        count += 1;
    }

    count
}

let mut gen = std::pin::pin!(format_each());

for value in [0, 5, 10, 25, 125, 87, 31] {
    match gen.as_mut().resume(value) {
        GeneratorState::Yielded(text) => println!("{text}"),
        GeneratorState::Complete(count) => {
            println!("printed {count} items");
            break;
        }
    }
}

This is obviously somewhat harder to use than just using the generator as an iterator but it does give you more abilities to use.

Accessing the first argument

If you run the code above (or look closely) then you might notice that the first argument passed into the generator is ignored. This usually isn’t what you want. In order to access the first argument you can use the argument! macro:

#[fauxgen::generator(yield = String, arg = u32)]
fn format_each() -> u32 {
    let mut count = 0;
    let mut value = argument!();

    while value < 100 {
        value = r#yield!(value.to_string());
        count += 1;
    }

    count
}

Note that using the argument! macro after you have called yield! is likely to result in a panic.

Macros

  • Declare an inline generator function.

Structs

Enums

Traits

  • The trait implemented by asynchronous generators.
  • The trait implemented by builtin generator types.

Attribute Macros

  • Declare a standalone generator function.