Crate remit

source ·
Expand description

Generators implemented through async/await syntax.

The pinned implementation is stack-based, and the boxed is heap-based. No fancy macros and a simple API. Values can be lazily or eagerly yielded.

This crate is inherently no-std, and the default alloc feature can be disabled.

Some behaviors exhibited by the lack of alloc are not part of the SemVer. For example, not awaiting before another remit, without alloc, is unspecified behavior.

Examples

General usage of unbounded generator.

use std::pin::pin;
use remit::{Generator, Remit};

async fn gen(remit: Remit<'_, usize>) {
    remit.value(42).await;
    // Does not need to be limited
    for i in 1.. {
        remit.value(i).await
    }
}
for item in pin!(Generator::new()).of(gen).take(10) {
    println!("{item}");
    // Prints 42, 1, 2, 3, 4, 5, 6, 7, 8, 9
}
assert_eq!(vec![42, 1, 2, 3], pin!(Generator::new()).of(gen).take(4).collect::<Vec<_>>());
/*
// Rust has trouble determining the lifetime
assert_eq!(
    vec![1],
    pin!(Generator::new())
        .of(|remit: Remit<'_, usize>| async move { remit.value(1).await; })
        .collect::<Vec<_>>(),
);
*/
assert_eq!(vec![42, 1], Generator::boxed(gen).take(2).collect::<Vec<_>>());
fn iter() -> impl Iterator<Item=usize> {
    Generator::boxed(gen)
}

Parameterized usage.

async fn scream<D: fmt::Display>(iter: impl Iterator<Item=D>, remit: Remit<'_, String>) {
    for person in iter {
        remit.value(format!("{person} scream!")).await
    }
    remit.value("... for ice cream!".to_string());
}
let expected: Vec<String> = ["You scream!", "I scream!", "We all scream!", "... for ice cream!"].iter().map(ToString::to_string).collect();
assert_eq!(
    expected,
    pin!(Generator::new()).parameterized(scream, ["You", "I", "We all"].iter()).collect::<Vec<String>>(),
);
assert_eq!(
    expected,
    Generator::boxed(|remit| scream(["You", "I", "We all"].iter(), remit)).collect::<Vec<String>>(),
);

Usage of a generator that only functions for 'static.

async fn gen(remit: Remit<'static, usize>) {
    remit.value(2).await;
    remit.value(3).await;
    remit.value(5).await;
    remit.value(7).await;
}
for item in Generator::boxed(gen) {
    println!("{item}");
}
assert_eq!(vec![2, 3, 5, 7], Generator::boxed(gen).collect::<Vec<_>>());
assert_eq!(vec![1], Generator::boxed(|remit| async move { remit.value(1).await; }).collect::<Vec<_>>());

fn iter() -> impl Iterator<Item=usize> {
    Generator::boxed(gen)
}

Unorthodox usage of “eagerly” yielding values.

// These implementations run successfully.
// However, they trigger creation of a buffer with alloc.
async fn no_await(remit: Remit<'_, usize>) {
    let _discard_future = remit.value(2);
    let _discard_future = remit.value(3);
    let _discard_future = remit.value(5);
    let _discard_future = remit.value(7);
}
assert_eq!(
    if cfg!(feature = "alloc") {
        vec![2, 3, 5, 7]
    } else {
        vec![7]
    },
    pin!(Generator::new()).of(no_await).collect::<Vec<_>>(),
);

async fn delay_await(remit: Remit<'_, usize>) {
    let first_remit = remit.value(11);
    remit.value(13).await;
    // Will poll-ready as the latter call implies all values are consumed.
    // A join will also do the same.
    first_remit.await;

    let _ = remit.value(17);
    let _ = remit.value(19);
    // Even though the future is done, the values were already sent.
}
assert_eq!(
    if cfg!(feature = "alloc") {
        vec![11, 13, 17, 19]
    } else {
        vec![13, 19]
    },
    pin!(Generator::new()).of(delay_await).collect::<Vec<_>>()
);

Usage of a boxed generator that borrows the parameter.

let data = String::from("hi");

async fn gen_implicit(data: &str, remit: Remit<'static, usize>) {
    remit.value(data.len()).await;
    remit.value(data.len()).await;
}

fn gen_explicit<'a>(data: &'a str, remit: Remit<'static, usize>) -> impl std::future::Future<Output=()> + 'a {
    async move {
        remit.value(data.len()).await;
        remit.value(data.len()).await;
    }
}

fn iter_explicit<'a>(data: &'a str) -> GeneratorIterator<'static, usize, impl std::future::Future<Output=()> + 'a> {
    Generator::boxed(|remit| gen_explicit(data, remit))
}

fn iter_implicit(data: &str) -> GeneratorIterator<'static, usize, impl std::future::Future<Output=()> + '_> {
    Generator::boxed(|remit| gen_implicit(data, remit))
}

for item in iter_explicit(&data) {
    dbg!(item);
}
for item in iter_implicit(&data) {
    dbg!(item);
}
for item in Generator::boxed(|remit| gen_explicit(&data, remit)) {
    dbg!(item);
}
for item in Generator::boxed(|remit| gen_implicit(&data, remit)) {
    dbg!(item);
}

Usage of a stack-based parameterized generator that borrows the parameter.

let data = String::from("hi");

async fn gen_implicit(data: &str, remit: Remit<'_, usize>) {
    remit.value(data.len()).await;
    remit.value(data.len()).await;
}
for item in pin!(Generator::new()).parameterized(gen_implicit, &data) {
    dbg!(item);
}

/// Does not work, as explicit lifetime definitions fail HRTB.
fn gen_explicit<'a: 'c, 'b: 'c, 'c>(data: &'a str, remit: Remit<'b, usize>) -> impl std::future::Future<Output=()> + 'c {
    async move {
        remit.value(data.len()).await;
        remit.value(data.len()).await;
    }
}
/* // See note above and https://github.com/rust-lang/rust/issues/114947
for item in pin!(Generator::new()).parameterized(gen_explicit, &data) {
    dbg!(item);
}
*/

Incorrect attempt of a stack-based generator.

/// Only accepts `'static`, so it needs to be boxed.
async fn gen(remit: Remit<'static, usize>) {
    remit.value(1).await;
}
// Fails to compile, because gen is only `'static` and pinning is for the stack.
for item in pin!(Generator::new()).of(gen) {
    println!("{item}");
}

Features

  • alloc - Enables the use of a boxed generator and multiple pending values. Defaults to enabled.

Structs

  • The storage used for iterators that poll a generator.
  • An iterator over generated values.
  • Allows a generator to provide values to an iterator. A generator that only accepts the 'static lifetime can only be used when boxed.

Traits

  • Trait used for relaxing the lifetime requirements of the generator storage.