Previously, I had a toy HTTP routing library Mizu, with usage similar to:
var server = try mizu.Server.init(gpa);
defer server.deinit();
try server.use(logger);
try server.get("/", handler);
try server.listen(address);
fn handler(a: std.mem.allocator, req: *mizu.Req, res: *mizu.Res) anyerror!void {
res.json(.{ name = "foo" });
}
Considering that allocator, request, and response are the most commonly used user interfaces, I expose them as parameters of the handler.
However, after the new Io was introduced, the parameters became four, so I merged them into:
fn usersHandler(ctx: *mizu.Context) anyerror!void {
_ = try ctx.json(.{ .users = &.{} });
}
At this point, I realized that when using Zig to build somewhat higher-level applications (for example, HTTP CRUD services), the allocator and IO are likely to need to be passed down from top to bottom. In that case, the best approach is probably to create a unified Context.
Or could it be made into syntactic sugar? Similar to when this struct has fields named allocator and io, member methods don’t need to declare allocator or io parameters, and can directly access them in the method body via self.allocator and self.io?
On second thought, this might be too implicit. Anyway, I’d like to hear what everyone thinks.
By the way, I think this Io idea is really cool, because if I haven’t misunderstood, I can switch my async backend implementation from multi-threaded to stackful coroutines, or single-threaded sharded scheduling, just by replacing a simple parameter, right?