Releases: romac/corophage
v0.3.0
Added
-
Program composition via
Yielder::invoke— invoke a sub-program from within another program, forwarding the inner program's effects through the outer yielder. The sub-program's effects must be a subset of the outer program's effects. This enables reusable effectful computations that can be composed together.#[effectful(Ask, Print)] fn greet() { let name: &str = yield_!(Ask("name?")); yield_!(Print(format!("Hello, {name}!"))); } #[effectful(Ask, Print, Log)] fn main_program() { yield_!(Log("Starting...")); invoke!(greet()); yield_!(Log("Done!")); }
-
invoke!()macro — call a sub-program inside an#[effectful]function. Expands to__y.invoke(expr).await. Outside#[effectful], emits a compile error. For the manualProgram::newAPI, usey.invoke(program).awaitdirectly. -
ForwardEffectstrait — internal trait used byYielder::invoketo forward each effect variant from a sub-program's coproduct through the outer yielder. -
#[effect(ResumeType)]proc macro — derive anEffectimpl by annotating a struct. Supports lifetimes, generics, and GAT resume types via'r.#[effect(bool)] pub struct Ask(i32); #[effect(&'r str)] pub struct GetConfig; #[effect(())] pub struct Log<'a>(pub &'a str);
-
#[effectful(Eff1, Eff2, ...)]proc macro — mark a function as an effectful computation. The macro transforms the return type toEff<...>, wraps the body inProgram::new, and enablesyield_!(expr)syntax inside the function.#[effectful(Ask, Log<'a>)] fn my_prog<'a>(msg: &'a str) -> bool { yield_!(Log(msg)); yield_!(Ask(42)) }
Supports a
sendflag forSend-able programs (#[effectful(Ask, send)]), automatic lifetime inference, and explicit lifetime annotation as the first argument. -
yield_!()fallback macro — emits a clear compile error when used outside an#[effectful]function. -
corophage-macroscrate — new proc-macro crate, added as a workspace member. Re-exported fromcorophagebehind themacrosfeature (enabled by default). -
macrosfeature flag — controls whether the proc macros are available. Enabled by default; disable withdefault-features = falseto opt out. -
Control<R>— new return type for effect handlers, parameterized by the resume typeRinstead of the full effect set. Handlers now returnControl::resume(value)orControl::cancel(), making them reusable across different effect sets.// Before: handler was coupled to the full effect set |_: Counter| CoControl::resume(42u64) |_: Ask| CoControl::<'static, Effects![Counter, Ask]>::cancel() // After: handler only knows its own resume type |_: Counter| Control::resume(42u64) |_: Ask| Control::<&str>::cancel()
-
Program::handle_all— attach multiple handlers at once from an HList. Handlers can cover any subset of the remaining effects, in any order.let handlers = hlist![ |_: Counter| Control::resume(42u64), |_: Ask| Control::resume("yes"), ]; Program::new(|y: Yielder<'_, Effects![Other, Counter, Ask]>| async move { ... }) .handle_all(handlers) .handle(|_: Other| Control::resume(())) .run_sync()
-
HandlersToEffectsimpls for stateful handlers —Fn(&mut S, E) -> Control<E::Resume<'a>>andAsyncFn(&mut S, E) -> Control<E::Resume<'a>>closures are now recognized byHandlersToEffects, enabling stateful handlers to work withhandle()andhandle_all().
Changed
- Removed
Send + Syncbounds fromEffect::Resume<'r>— theEffecttrait no longer requires resume types to beSend + Sync. This allows effects with non-Sendresume types (e.g.,Rc<str>) when used with non-Sendcoroutines (Co/Program::new). The bounds are still enforced forSend-able coroutines (CoSend/Program::new_send) via the existingfor<'r> Resumes<'r, ...>: Send + Syncconstraint. The#[effect]proc macro anddeclare_effect!macro no longer addSend + Syncbounds on generic type parameters used in the resume type. Program::handle()is now order-independent —.handle()now usesCoproductSubsetter(like.handle_all()) to remove the handled effect from the remaining set, so handlers can be attached in any order. Handlers passed to the low-levelsync::run/asynk::runfunctions must still match theEffects![...]order.CoControlis now internal — replaced byControl<R>in user-facing code.CoControlis still used internally by the runner loop but is no longer exported.- Prelude updated —
CoControlremoved from prelude,Controladded.
v0.2.0
Added
-
Programtype — a builder-style API for assembling and running effect-handled computations. This is now the recommended way to use the library for most users.The key feature is incremental handler attachment: handlers are added one at a time via
.handle(), which means a partially-handled program is a first-class value you can pass around, store, or extend later. The compiler tracks which effects are still unhandled and only permits running the computation once all effects have a handler.// Define a computation once... let program = Program::new(|yielder: Yielder<'_, Effs>| async move { let n = yielder.yield_(Counter).await; let answer = yielder.yield_(Ask("question")).await; (answer, n) }); // ...attach handlers incrementally, e.g. in different modules or call sites... let program = program.handle(|_: Counter| CoControl::resume(42u64)); let program = program.handle(|_: Ask| CoControl::resume("yes")); // ...and run only when all effects are handled. let result = program.run_sync();
Available constructors and methods:
Program::new(f)— creates a program from an async closureProgram::new_send(f)— creates aSend-able program (for use withtokio::spawn)Program::from_co(co)— wraps an existingCo/CoSendcoroutine.handle(handler)— attaches the next handler (in effect declaration order); type-checked at compile time.run_sync()— executes synchronously, returnsResult<R, Cancelled>.run_sync_stateful(&mut state)— executes synchronously with shared mutable state.run()— executes asynchronously, returnsResult<R, Cancelled>.run_stateful(&mut state)— executes asynchronously with shared mutable state
-
handlefree function — functional alternative to.handle(), useful when incrementally building up a program across call sites without method chaining:let p = handle(p, |_: Counter| CoControl::resume(42u64)); let p = handle(p, |_: Ask| CoControl::resume("yes")); p.run_sync()
-
All public items are now documented.
Full Changelog: v0.1.0...v0.2.0