Last active
June 4, 2023 17:37
-
-
Save sug0/5c20b140eb6ad57ed801db17d6b69668 to your computer and use it in GitHub Desktop.
Draft design of stackless generators in stable Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()), | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! 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