* effers ergonomic effect handlers in rust ** how to use *** defining effects effects are defined with traits #+begin_src rust trait Printer { fn print(&self, s: &str); fn available() -> bool; } trait Logger { fn debug(&mut self, s: &str); fn info(self, s: &str); } #+end_src functions can take ~self~, ~&self~, ~&mut self~, or no ~self~ parameter. at this point ~self~ parameters with a specified type (like ~self: Box~) 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 #+begin_src rust #[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 } #+end_src **** name the first token (~MyCoolProgram~) will be the name of the program. this is optional, and can be skipped: #+begin_src rust #[program( Printer(print(&self) as p, available as printer_available), Logger(debug(&mut self), info(self)) )] #+end_src 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 #+begin_src rust 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) } } #+end_src *** 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 #+begin_src rust let result: u8 = MyCoolProgram.add(IoPrinter).add(FileLogger).run(3); assert_eq!(result, 6); #+end_src ** examples - [[./examples/main.rs][main: general use case]] - [[./examples/clone.rs][clone: how cloning and copying programs works]] - [[./examples/module.rs][module: effects from other modules are supported]]