[][src]Module genawaiter::stack

This module implements a generator which doesn't require allocation.

You can create a generator with the unsafe_create_generator! macro:

async fn producer(co: Co<'_, i32>) { /* ... */ }

unsafe_create_generator!(gen, producer);

This is safe as long as you don't do anything silly with the Co object. (See below for the fine print. If you cannot abide the unsafe keyword, use an allocating generator instead.)

The macro is a shortcut for creating both a generator and its backing state (called a Shelf, to avoid confusion with the GeneratorState enum). If you (or your IDE) dislike macros, you can also do the bookkeeping by hand:

let mut shelf = Shelf::new();
let gen = unsafe { Gen::new(&mut shelf, producer) };

See the crate-level docs for a guide on how to use the generator after it's been created.

Safety

Do not let the Co object escape the scope of the generator. By time the generator completes, the Co object should already have been dropped. If this invariant is not upheld, memory unsafety will result.

Afaik, the Rust compiler is not flexible enough to let you express this invariant in the type system, but I would love to be proven wrong!

Examples

Using Iterator

Generators implement Iterator, so you can use them in a for loop:

unsafe_create_generator!(gen, odd_numbers_less_than_ten);
for n in gen {
    println!("{}", n);
}

Collecting into a Vec

unsafe_create_generator!(gen, odd_numbers_less_than_ten);
let xs: Vec<_> = gen.into_iter().collect();
assert_eq!(xs, [1, 3, 5, 7, 9]);

Using resume()

unsafe_create_generator!(gen, odd_numbers_less_than_ten);
assert_eq!(gen.resume(), GeneratorState::Yielded(1));
assert_eq!(gen.resume(), GeneratorState::Yielded(3));
assert_eq!(gen.resume(), GeneratorState::Yielded(5));
assert_eq!(gen.resume(), GeneratorState::Yielded(7));
assert_eq!(gen.resume(), GeneratorState::Yielded(9));
assert_eq!(gen.resume(), GeneratorState::Complete(()));

Using an async closure (nightly Rust only)

This example is not tested
unsafe_create_generator!(gen, async move |co| {
    co.yield_(10).await;
    co.yield_(20).await;
});
assert_eq!(gen.resume(), GeneratorState::Yielded(10));
assert_eq!(gen.resume(), GeneratorState::Yielded(20));
assert_eq!(gen.resume(), GeneratorState::Complete(()));

Using an async closure faux·sure (works on stable Rust)

unsafe_create_generator!(gen, |co| async move {
    co.yield_(10).await;
    co.yield_(20).await;
});
assert_eq!(gen.resume(), GeneratorState::Yielded(10));
assert_eq!(gen.resume(), GeneratorState::Yielded(20));
assert_eq!(gen.resume(), GeneratorState::Complete(()));

Passing ordinary arguments

This is just ordinary Rust, nothing special.

async fn multiples_of(num: i32, co: Co<'_, i32>) {
    let mut cur = num;
    loop {
        co.yield_(cur).await;
        cur += num;
    }
}

unsafe_create_generator!(gen, |co| multiples_of(10, co));
assert_eq!(gen.resume(), GeneratorState::Yielded(10));
assert_eq!(gen.resume(), GeneratorState::Yielded(20));
assert_eq!(gen.resume(), GeneratorState::Yielded(30));

Passing resume arguments

You can pass values into the generator.

Note that the first resume argument will be lost. This is because at the time the first value is sent, there is no future being awaited inside the generator, so there is no place the value could go where the generator could observe it.

async fn check_numbers(co: Co<'_, (), i32>) {
    let num = co.yield_(()).await;
    assert_eq!(num, 1);

    let num = co.yield_(()).await;
    assert_eq!(num, 2);
}

unsafe_create_generator!(gen, check_numbers);
gen.resume_with(0);
gen.resume_with(1);
gen.resume_with(2);

Returning a completion value

You can return a completion value with a different type than the values that are yielded.

async fn numbers_then_string(co: Co<'_, i32>) -> &'static str {
    co.yield_(10).await;
    co.yield_(20).await;
    "done!"
}

unsafe_create_generator!(gen, numbers_then_string);
assert_eq!(gen.resume(), GeneratorState::Yielded(10));
assert_eq!(gen.resume(), GeneratorState::Yielded(20));
assert_eq!(gen.resume(), GeneratorState::Complete("done!"));

Structs

Co

This object lets you yield values from the generator by calling the yield_ method.

Gen

This is a generator which stores all its state without any allocation.

Shelf

This data structure holds the transient state of an executing generator.