This pattern was designed while working on a init system which has as the critical requirement to never crash or exit, no matter what happens.
But usually you need to set up some stuff before going into the main loop. The simple, but (to me) not so obvious solution was this:
pub fn main() !u8 {
// setup phase, load all data here
var data = try loadData();
defer data.deinit();
log.info("ready.", .{});
coreLoop(&data);
log.info("clean application exit.", .{});
// teardown phase, clean up your resources here!
return 0;
}
fn coreLoop(data: *Data) void {
while(true) {
// Do your application logic here!
}
}
By splitting main() into a setup phase, coreLoop()
and teardown phase, we can put the guarantee to not error at runtime into the function contract of coreLoop()
.
coreLoop()
can't return an error, so we're never able to leave the function unexpectedly and it will be a compile error to do so.
This makes reviewing the code much easier as well.
Top comments (1)
Nice! This is the same design idea behind
std.event.Loop.runDetached
: since the async function will not have a surrounding context capable of handling errors, the function you pass in as an argument is required to returnvoid
.github.com/ziglang/zig/blob/master...