I'm too dumb for Zig's new IO interface

Here’s repro - not exactly the same (I’ve probably found an easier way to trigger the bug) but fails with zig default readVec impl and passes with my readVec2

const std = @import("std");

pub fn main() !void {
    var buf: [255]u8 = undefined;
    var x: X = .{
        .reader = .{
            .vtable = &.{
                .stream = stream,
                .readVec = readVec2,
            },
            .buffer = &buf,
            .seek = 0,
            .end = 0,
        },
    };

    _ = try x.reader.peek(1);
    const y = try x.reader.peek(2);
    std.debug.print("{s}\n", .{y});
}

const X = struct {
    reader: std.io.Reader,
};

fn stream(r: *std.io.Reader, _: *std.io.Writer, _: std.io.Limit) std.io.Reader.StreamError!usize {
    // This was a data buffer we got from curl
    const data = "xyz";

    const newcap = r.bufferedLen() + data.len;

    if (newcap > r.buffer.len) {
        // The original code would pause reading the curl stream.
        return 0;
    }

    // Otherwise we just try to fit as much as we can from the curl buffer into our r.buffer
    // It's not so simple in the original code because curl does not allow changing the buffer size,
    // so our buffer size needs to be twice the curl buffer size and we can't fill the buffer partially,
    // so we always have at least one full curl bufsize to fill.
    r.rebase(newcap) catch {};
    @memcpy(r.buffer[r.end .. r.end + data.len], data);
    r.end += data.len;

    return 0;
}

// The patched readVec impl which works
pub fn readVec2(r: *std.io.Reader, data: [][]u8) std.io.Reader.Error!usize {
    const first = data[0];
    if (first.len >= r.buffer.len - r.end) {
        var writer: std.io.Writer = .{
            .buffer = first,
            .end = 0,
            .vtable = &.{ .drain = std.io.Writer.fixedDrain },
        };
        const limit: std.io.Limit = .limited(writer.buffer.len - writer.end);
        return r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
            error.WriteFailed => unreachable,
            else => |e| return e,
        };
    }
    var writer: std.io.Writer = .{
        .buffer = r.buffer,
        .end = r.end,
        .vtable = &.{ .drain = std.io.Writer.fixedDrain },
    };
    const limit: std.io.Limit = .limited(writer.buffer.len - writer.end);
    // This is the change - the original code does r.end += r.vtable.stream(...)
    const n = r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
        error.WriteFailed => unreachable,
        else => |e| return e,
    };
    r.end += n;
    return 0;
}
2 Likes