effers-derive | ||
examples | ||
src | ||
.gitignore | ||
Cargo.toml | ||
README.org |
effers
ergonomic effect handlers in rust
how to use
defining effects
effects are defined with traits
trait Printer {
fn print(&self, s: &str);
fn available() -> bool;
}
trait Logger {
fn debug(&mut self, s: &str);
fn info(self, s: &str);
}
functions can take self
, &self
, &mut self
, or no self
parameter. at this point self
parameters with a specified type (like self: Box<Self>
) are not supported
defining a program
programs are defined as a normal function, with the added program
attribute, which specifies (optional) a name for the program, and (required) the list of effects and corresponding functions that are used
#[effers::program(MyCoolProgram =>
Printer(print(&self) as p, available as printer_available),
Logger(debug(&mut self), info(self))
)]
fn my_program(val: u8) -> u8 {
if printer_available() {
p("hey hi hello");
}
debug("this is a debug-level log");
info("this is a info-level log");
val + 3
}
name
the first token (MyCoolProgram
) will be the name of the program. this is optional, and can be skipped:
#[program(
Printer(print(&self) as p, available as printer_available),
Logger(debug(&mut self), info(self))
)]
if skipped, the default name will be the program function's name (my_program
) in PascalCase (MyProgram
)
listing effects
effects are listed by writing the trait's name, followed by a parenthesized list of the functions that will be used
listing effect functions
due to limitations of proc-macros, it's unknown what kind of self
parameter the function takes, if any, and so it has to be explicitly specified (if you have ideas on how to fix this, please open a PR!): here's how each type is specified:
fn print();
:print
fn print(self);
:print(self)
fn print(mut self);
:print(self)
fn print(&self);
:print(&self)
fn print(&mut self);
:print(&mut self)
effect function aliases
functions can be given an alias using the as
keyword (print(&self) as p
) so that the function can be called by a different name inside the program
defining effect handlers
effect handlers are defined by declaring a struct, and implementing the corresponding trait on it
struct IoPrinter;
impl Printer for IoPrinter {
fn print(&self, s: &str) {
println!("{}", s)
}
fn available() -> bool {
true
}
}
struct FileLogger;
impl Logger for FileLogger {
fn debug(&mut self, s: &str) {
println!("debug: {}", s)
}
fn info(self, s: &str) {
println!("info: {}", s)
}
}
running programs
programs are run by providing the corresponding handlers in the order listed in the program definition, and finally calling the run
method, providing it the required parameters
let result: u8 = MyCoolProgram.add(IoPrinter).add(FileLogger).run(3);
assert_eq!(result, 6);