Skip to content

Instantly share code, notes, and snippets.

@sug0
Last active June 4, 2023 17:37
Show Gist options
  • Save sug0/5c20b140eb6ad57ed801db17d6b69668 to your computer and use it in GitHub Desktop.
Save sug0/5c20b140eb6ad57ed801db17d6b69668 to your computer and use it in GitHub Desktop.
Draft design of stackless generators in stable Rust
use std::pin::Pin;
use std::ops::ControlFlow;
use syn;
use quote;
/// Stackless generator, with no internal data stored
/// in itself.
pub trait Generator {
/// Data persisted across yield points, as well as
/// references to data stored elsewhere.
type Ctx<'ctx>;
/// The yield type of the generator.
type Yield;
/// The return type of the generator.
type Return;
/// Resume the generator.
fn resume<'ctx>(&mut self, ctx: Pin<&mut Self::Ctx<'ctx>>)
-> ControlFlow<Self::Return, Self::Yield>;
}
/// Stackless generator with no context data.
pub trait LeanGenerator {
/// The yield type of the generator.
type Yield;
/// The return type of the generator.
type Return;
/// Resume the generator.
fn lean_resume(&mut self) -> ControlFlow<Self::Return, Self::Yield>;
}
impl<G> LeanGenerator for G
where
G: for<'ctx> Generator<Ctx<'ctx> = ()>,
{
type Yield = <G as Generator>::Yield;
type Return = <G as Generator>::Return;
fn lean_resume(&mut self) -> ControlFlow<Self::Return, Self::Yield> {
self.resume(Pin::new(&mut ()))
}
}
macro_rules! generator {
(ctx: $type:ty, code: $($tt:tt)*) => {
// return unit
()
};
}
macro_rules! generator_next {
($control_flow:expr $(,)?) => {
match $control_flow {
::core::ops::ControlFlow::Continue(x) => x,
::core::ops::ControlFlow::Break(x) => break x,
}
}
}
fn main() {
// accept a context to evaluate the generator;
// data accessed across yield points must be moved
// to the context; the context can be accessed through
// the keyword `self`, and is mutable.
//
// since there is no data stored in the generator,
// it doesn't need to be pinned; it may make sense
// to pin the context, though, if it holds references
// to itself
//
// actually, every reference in the context should
// probably be pinned, across yield points
let _: syn::Block = syn::parse2(quote::quote! {{
yield 5;
loop {
if self.i == 5 {
break;
}
yield self.i;
self.i += 1;
}
while true { return 1; }
}})
.unwrap();
let _gen = generator! {
ctx: Ctx<'_>,
code: {
loop {
if self.i == 5 {
break;
}
yield self.i;
self.i += 1;
}
},
};
let mut gen = wut();
let () = loop {
let i = generator_next!(
gen.resume(Pin::new(&mut ())),
);
println!("yielded: {i}");
};
}
/*
struct Ctx {
i: i32,
}
let mut ctx = Ctx { i: 0 };
let mut gen = generator! {
loop {
if self.i == 5 {
break;
}
yield self.i;
self.i += 1;
}
};
while let ControlFlow::Continue(i) = gen.resume(Pin::new(&mut ctx)) {
println!("{i}");
}
*/
fn wut()
-> impl for<'ctx> Generator<Ctx<'ctx> = (), Yield = i32, Return = ()>
{
Gen::Init
}
enum Gen {
Init,
Yield0,
Yield1,
Yield2,
Done,
}
impl Generator for Gen {
type Ctx<'ctx> = ();
type Return = ();
type Yield = i32;
fn resume<'ctx>(&mut self, _ctx: Pin<&mut Self::Ctx<'ctx>>)
-> ControlFlow<Self::Return, Self::Yield>
{
match self {
Gen::Init => {
*self = Gen::Yield0;
ControlFlow::Continue(0)
},
Gen::Yield0 => {
*self = Gen::Yield1;
ControlFlow::Continue(1)
},
Gen::Yield1 => {
*self = Gen::Yield2;
ControlFlow::Continue(2)
},
Gen::Yield2 => {
*self = Gen::Done;
ControlFlow::Continue(3)
},
Gen::Done => ControlFlow::Break(()),
}
}
}
//! NOTES ON CODE GENERATION
// -----------------------------------------------------------------------------
generator! {
let x = 1234;
yield 4;
x.do_thing(); // `x` is used after `yield`, error out
}
// -----------------------------------------------------------------------------
generator! {
let mut x = 1;
while x < 10 {
x += 1;
yield x;
}
// `x` is not used after `yield`, but it is
// used in a loop, so it escapes! it should be
// stored in a `ctx`
}
// -----------------------------------------------------------------------------
generator! {
let mut x = 1;
if x < 10 {
yield x;
} else {
// no-op
()
}
// an if-expr is fine
}
// -----------------------------------------------------------------------------
generator! {
let mut x = 1;
if x < 10 {
while x < 10 {
yield x;
x += 1;
}
} else {
// no-op
()
}
// we must still check recursively inside each
// expression! this is not fine, as there is a
// loop
}
// -----------------------------------------------------------------------------
generator! {
let x = 1234;
{
// yielding the value is fine, it will get moved
// by rust anyway, so it can't be used later
yield x;
// shadowing the old binding
let x = 4567;
// used old binding id after a yield, but
// not the new one! this is fine
println!("{x}");
// new yield happens here
yield 4;
// this would not be fine:
// println!("{x}");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment