::next_gen

Safe generators on stable Rust.

Repository Latest version Documentation MSRV unsafe forbidden License CI

Examples

Reimplementing a range iterator

```rust use ::next_gen::prelude::*;

[generator(yield(u8))]

fn range (start: u8, end: u8) { let mut current = start; while current < end { yield_!(current); current += 1; } }

mkgen!(let generator = range(3, 10)); asserteq!( generator.into_iter().collect::>(), (3 .. 10).collect::>(), ); ```

Implementing an iterator over prime numbers using the sieve of Eratosthenes

```rust use ::next_gen::prelude::*;

enum NeverSome {}

/// Generator over all the primes less or equal to up_to.

[generator(yield(usize))]

fn primesupto (upto: usize) -> Option { if upto < 2 { return None; } let mut sieve = vec![true; upto.checkedadd(1).expect("Overflow")]; let mut p: usize = 1; loop { p += 1 + sieve .get(p + 1 ..)? .iter() .position(|&isprime| isprime)? ; yield!(p); let p2 = if let Some(p2) = p.checkedmul(p) { p2 } else { continue }; if p2 >= upto { continue; } sieve[p2 ..] .itermut() .stepby(p) .foreach(|isprime| *isprime = false) ; } }

mkgen!(let primes = primesupto(10000)); for prime in primes { assert!( (2usize ..) .takewhile(|&n| n.saturating_mul(n) <= prime) .all(|n| prime % n != 0) ); } ```

Defining an iterator with self-borrowed state

This is surely the most useful feature of a generator.

Consider, for instance, the following problem:

rust fn iter_locked (elems: &'_ Mutex<Set<i32>>) -> impl '_ + Iterator<Item = i32>

Miserable attempts without generators

No matter how hard you try, without using unsafe, or some other unsafe-using self-referential library/tool, you won't be able to feature such a signature!

But with generators this is easy:

```rust use ::next_gen::prelude::*;

[generator(yield(i32))]

fn geniterlocked (mutexedelems: &' Mutex>) { let lockedelems = mutexedelems.lock().unwrap(); for elem in lockedelems.iter().copied() { yield!(elem); } } ```

and voilà!

That #[generator] fn is the key constructor for our safe self-referential iterator!

Now, instantiating an iterator off a self-referential generator has a subtle aspect, muck alike that of polling a self-referential Future (that's what a missing Unpin bound means): we need to get it pinned before it can be polled!

About pinning "before use", and the two forms of pinning

  1. Getting a Future:

    rust let future = async { ... }; // or let future = some_async_fn(...);

    • Pinning an instantiated Future in the heap (Boxed):

    rust // Pinning it in the heap (boxed): let mut pinned_future = Box::pin(future) // or, through an extension trait (`::futures::future::FutureExt`): let mut pinned_future = future.boxed() // this also incidentally `dyn`-erases the future.

    • Now we can return it, or poll it:

      rust if true { pinned_future.as_mut().poll(...); } // and/or return it: return pinned_future;

      • Pinning an instantiated Future in the stack (pinned to the local scope):

    ``rust use ::some_lib::some_pinning_macro as stack_pinned; // Pinning it in the "stack" stack_pinned!(mut future); /* the above shadowsfuture`, thus acting as: let mut future = magic::Stack::pin(future); // */

    // Let's rename it for clarity: let mut pinned_future = future; ```

    • Now we can poll it / use it within the current stack frame, but we cannot return it.

      rust pinned_future.as_mut().poll(...)

Well, it turns out that for generators it's similar:

  1. Once you have a #[generator] fn "generator constructor"

    ```rust use ::next_gen::prelude::*;

    [generator(yield(u8))]

    fn foo () { yield_!(42); } ```

  2. Instantiation requires pinning, and thus:

    • Stack-pinning: cheap, no_std compatible, usable within the same scope. But it cannot be returned.

      ```rust mk_gen!(let mut generator = foo());

      // can be used within the same scope asserteq!(generator.next(), Some(42)); asserteq!(generator.next(), None);

      // but it can't be returned // return generator; /* Error, can't return borrow to local value */ ```

    • Heap-pinning: a bit more expensive, requires an ::allocator or not being no_std, but the so-pinned generator can be returned.

      ```rust mk_gen!(let mut generator = box foo());

      // can be used within the same scope if somecondition { asserteq!(generator.next(), Some(42)); assert_eq!(generator.next(), None); }

      // and/or it can be returned return generator; // OK ```

So, back to our example, this is what we need to do:


```rust use ::next_gen::prelude::*;

/// We already have:

[generator(yield(i32))]

fn geniterlocked (mutexedelems: &' Mutex>) ...

/// Now let's wrap-it so that it yields a nice iterator: fn iterlocked (mutexedelems: &'_ Mutex>) -> impl '_ + Iterator { if true { // One possible syntax to instantiate the generator mkgen!(let generator = box geniterlocked(mutexedelems)); generator } else { // or, since we are box-ing, we can directly do: geniterlocked.callboxed((mutexedelems, )) } // : Pin>> // : impl '_ + Iterator }

let mutexedelems = Mutex::new([27, 42].iter().copied().collect::>()); let mut iter = iterlocked(&mutexedelems); asserteq!(iter.next(), Some(27)); asserteq!(iter.next(), Some(42)); asserteq!(iter.next(), None); ```

Resume arguments

This crate has been updated to support resume arguments: the Generator trait is now generic over a ResumeArg parameter (which defaults to ()), and its .resume(…) method now takes a parameter of that type:

rust let _: GeneratorState<Yield, Return> = generator.as_mut().resume(resume_arg);

this makes it so the yield_!(…) expressions inside the generator evaluate to ResumeArg rather than ():

rust let _: ResumeArg = yield_!(value);

Macro syntax

In order to express this using the #[generator] attribute, add a resume(Type) parameter to it:

```rust use ::next_gen::prelude::*;

type ShouldContinue = bool;

[generator(yield(i32), resume(ShouldContinue))]

fn g () { for i in 0 .. { let shouldcontinue = yield!(i); if should_continue.not() { break; } } }

mkgen!(let mut generator = g()); assert!(matches!( generator.asmut().resume(bool::default()), // <- this resume arg is being ignored GeneratorState::Yielded(0), )); assert!(matches!( generator.asmut().resume(true), GeneratorState::Yielded(1), )); assert!(matches!( generator.asmut().resume(true), GeneratorState::Yielded(2), )); assert!(matches!( generator.as_mut().resume(true), GeneratorState::Yielded(3), ));

assert!(matches!( generator.as_mut().resume(false), GeneratorState::Complete, )); ```

If you don't want to ignore/disregard the first resume argument (the "start argument" we could call it), then you can append a as <binding> after the resume(ResumeArgTy) annotation:

```rust use ::next_gen::prelude::*;

type ShouldContinue = bool;

[generator(

yield(i32),
resume(ShouldContinue) as mut should_continue,

)] fn g () { for i in 0 .. { if shouldcontinue.not() { break; } shouldcontinue = yield_!(i); } } ```

Features

Performance

The crate enables no-allocation generators, thanks the usage of stack pinning. When used in that fashion, it should thus be close to zero-cost.

Ergonomics / sugar

A lot of effort has been put into macros and an attribute macro providing the most ergonomic experience when dealing with these generators, despite the complex / subtle internals involved, such as stack pinning.

Safe

Almost no unsafe is used, the exception being:

no_std support

This crates supports #![no_std]. For it, just disable the default "std" feature:

```toml

toml [dependencies] next-gen.version = "..." next-gen.default-features = false # <- ADD THIS to disable `std`&`alloc` for `no_std` compat next-gen.features = [ "alloc", # If your no_std platform has access to allocators. # "std", # `default-features` bundles this. ]

Idea

Since generators and coroutines rely on the same internals, one can derive a safe implementation of generators using the async / await machinery, which is only already in stable Rust.

A similar idea has also been implemented in https://docs.rs/genawaiter.