Zig's HTTP frameworks will probably all have a Ctx like Go in the future

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?

3 Likes

This would break readability, I think a userspace ctx : struct is fine, and easy to understand even for non zig reader, but syntax level support would prevent non zig user from reading the code as is.

4 Likes

Yes! it does require those implementations exist ofc, and unfortunately the only usable implementation currently is only std.Io.Threaded.


a context type is a common and encouraged pattern, especially as the data you commonly pass around grows.

As for how that should be integrated into a library, it depends on the library and how it will be used.

Giving the user as much control over it is a must, otherwise you force them to resort to global variables which can be problematic.
The easiest is to just have a library context and a user defined context that both get passed around.

If the libraries context is small and doesn’t change, then it could be integrated into the users context. Ideally your api would be structured in a way that library code doesn’t have to inject its context, but that is possible if necessary, though such cases should consider just seperating them.

1 Like

Implicit context system

I am aware of odins context system, and figured something like that is what they mean by syntax sugar.

zig will likely never have such a thing. Just different priorities.

1 Like

sure

but now we already have io and allocator, who knows what will be added in near future

at least in my lib main “interface” has getAllocator “method”

as result i need to use just one…

async and std improvements were planned a long long time ago. Them manifesting the way they have with std.Io is surprising, but not unexpected.

I can not think of anything planned that could result in zig getting a context system.

3 Likes