# Sketch of type-safer buffered IO

**URL:** <https://ziggit.dev/t/sketch-of-type-safer-buffered-io/4810>\
**Category:** Brainstorming\
**Tags:** standard-library\
**Created:** [June 21, 2024, 10:32am UTC](https://ziggit.dev/t/sketch-of-type-safer-buffered-io/4810 "2024-06-21T10:32:51Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![matklad](https://ziggit.dev/user_avatar/ziggit.dev/matklad/32/2462_2.png) [@matklad](https://ziggit.dev/u/matklad)\
**Post date:** [June 21, 2024, 10:32am UTC](https://ziggit.dev/t/sketch-of-type-safer-buffered-io/4810/1 "2024-06-21T10:32:51Z")

</div>

Zig’s `Reader` has this function:

```zig
/// Reads 1 byte from the stream or returns `error.EndOfStream`.
pub fn readByte(self: Self) anyerror!u8 {
    var result: [1]u8 = undefined;
    const amt_read = try self.read(result[0..]);
    if (amt_read < 1) return error.EndOfStream;
    return result[0];
}

```

Higher-level operations like “read a line” bottom out in calling this function in a loop (very approximate code):

```zig
fn readLine(reader: AnyReader, buffer: []u8) []u8 {
    for(buffer, 0..) |&byte, index| {
        byte.* = try reader.readByte();
        if (byte.* == '\n') return buffer[..index];
    }
}

```

This code has three performance bugs:

1. In the worst-case, it does one syscall per byte of input
2. It does one virtual call per byte of input
3. It doesn’t use SIMD and is not vectorizable — there’s simply no slice of memory we can run SIMD over here

Now, the first (and only the first) issue can be fixed by wrapping a reader into a buffered reader, but that still leaves a couple of performance rakes lying dangerously around:

```zig
fn uses_reader(reader: AnyReader) !void

```

This signature gives raise to at least three distinct possibilities:

1. The function isn’t using `readByte`-derived APIs, in which case it is fine to pass something like `std.fs.File` in directly
2. The function does call something like `readLine` internally, so the caller _must_ supply a buffered reader.
3. Out of caution, the function _internally_ wraps a reader into a buffered reader, so the user _must not_ pass a buffered reader, as that would leave to unnecessary double buffering.

If the caller’s and callee expectations mismatch, there’s a perf bug! It’s also not hard to imagine a situation where a library gets refactored from 2. to 3. to fix perf issues for one user, creating new perf issues for other users who did buffer already.

I think the right solution here is to move byte-oriented API to a buffered reader, such that, if you want to call `readLine`, you function signature tells the caller that they need to supply a buffered reader. That’s basically how Rust `Read`, `BufRead` and `BufReader` are set up.

I don’t think I am quite ready to submit a PR to Zig repo with this (relatively large scale) change, but I couldn’t help but sketch the API this morning! Here’s the result:

> <https://gist.github.com/matklad/cc25c31417e73c69f11d174dc2df71c6>

This is the dynamically-dispatched part of the API. For the generic part, I think it basically boils down to rewriting the existing `fn BufferedReader` along the lines of `fn GenericReader(`
