eg. iterators and panic could be implemented on top of an effect system like this.
To be less vague: I’m imagining the Generator trait looking something like this:
enum GeneratorState<Y, C> {
Yielded(Y),
Complete(C),
}
trait Generator {
type Yield;
type Resume;
type Return;
fn start(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
fn resume(&mut self, resume: Self::Resume) -> GeneratorState<Self::Yield, Self::Return>;
}
If a generator has Yield == ! then it’s just an ordinary function. It’s
allowed to use the stack and it doesn’t need to be compiled to a state machine.
Additionally, if a generator has Yield != ! but Resume == ! then it’s also
an ordinary function. We don’t need to be able to resume it, so we also don’t
need to compile it to a state machine. It needs to be able to yield though, and that can be implemented as unwinding.
With effects implemented like this, panicking can be implemented as an effect:
effect Panic {
fn panic<T: Any + Send + 'static>(val: T) -> !;
}
// The default handler we enter main with (unless built with panic = "abort")
struct UnwindPanic;
// yields with Box<Any + Send + 'static> and resumes with !
handler UnwindPanic for Panic yields Box<Any + Send + 'static> -> ! {
fn panic<T: Any + Send + 'static>(val: T) -> ! {
yield (box val)
}
}
// The handler inside drop calls, during panics, and when built with panic = "abort".
struct AbortPanic;
handler AbortPanic for Panic yields ! {
fn panic<T: Any + Send + 'static>(_val: T) -> ! {
libc::exit(-1)
}
}
fn catch_unwind<F: FnOnce() -> R>(f: F) -> thread::Result<R> {
let g = do UnwindPanic { f() };
match g.start() {
Yielded(e) => Err(e),
Complete(x) => Ok(x),
}
}
For iterators, there could be an effect like this:
effect Iter<T> {
fn item(t: T);
}
// A handler to drive the iterator.
struct For;
handler<T> For for Iter<T> yields T {
fn item(t: T) {
yield t;
}
}
struct EffIterator<G> {
gen: G,
started: bool,
}
fn eff_iter<T, F>(f: F) -> EffIterator<impl Generator<Yield = T, Resume = (), Return = ()>>
where F: FnOnce() effect Iter<T> {
let gen = do For { f() };
EffIterator {
gen: gen,
started: false,
}
}
impl<T, G> Iterator for EffIterator<G>
where G: Generator<Yield = T, Resume = (), Return = ()> {
type Item = T;
fn next(&mut self) -> Option<T> {
let val = if self.started {
self.0.start()
self.started = true;
} else {
self.0.resume(())
};
match val {
Yielded(t) => Some(t),
Complete(()) => None,
}
}
}
Which you could then use like this:
// make a function with the Iter effect
let count_to_ten = || {
for i in 0..10 {
Iter::item(i);
}
};
// turn it into an iterator, use it to count to ten
for val in eff_iter(count_to_ten) {
println!("got: {}", val);
}