In his Don’t forget to flush talk, Andrew mentions one of the advantages of having an io instance passed as an argument is that function purity becomes apparent. I love that.
However, sometimes standard library types store their own io instance, making this function purity unapparent at first glance.
take this simple http server program:
const std = @import("std");
const print = std.debug.print;
const Io = std.Io;
pub fn main(init: std.process.Init) !void {
const io = init.io;
const address = try Io.net.IpAddress.parse("0.0.0.0", 8000);
var server = try address.listen(io, .{});
defer server.deinit(io);
const stream = try server.accept(io);
defer stream.close(io);
var read_buffer: [1024] u8 = @splat(0);
var write_buffer: [1024] u8 = @splat(0);
var reader = stream.reader(io, &read_buffer);
var writer = stream.writer(io, &write_buffer);
var http_server = std.http.Server.init(&reader.interface, &writer.interface);
// server will wait for requests in the line below
var request = try http_server.receiveHead();
var body_buffer: [1024]u8 = @splat(0);
const bodyReader = request.readerExpectNone(&body_buffer);
var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator);
const alloc = arena.allocator();
const body = try bodyReader.readAlloc(alloc, @intCast(request.head.content_length orelse 0));
std.debug.print("{any}", .{body});
const html = try readHtml(alloc, io);
try request.respond(html, .{.status = .ok});
}
fn readHtml(alloc: std.mem.Allocator, io: Io) ![] u8 {
return try std.Io.Dir.cwd().readFileAlloc(io, "./index.html", alloc, Io.Limit.unlimited);
}
If you look into std.http.Server.receiveHead you’ll see that defines a std.http.Server.Reader, which contains a std.Io.Reader, which in turn contains an Io instance.
Why does/should std.Io.Reader contain an Io instance within it, instead of requiring it be passed in all its functions?
I guess it’s a little less verbose, but we do lose this ability to tell at a glance which functions may do Io (and possibly block).
More generally, what’s the rule of thumb to pass Io instances (or for that matter, std.mem.Allocator instances) to every function call, vs having a struct store it and pass it “implicitly”?
Let me know if any clarification is needed