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

**URL:** <https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645>\
**Category:** Media\
**Created:** [August 23, 2025, 8:06am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645 "2025-08-23T08:06:07Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![vpol](https://ziggit.dev/user_avatar/ziggit.dev/vpol/32/3375_2.png) [@vpol](https://ziggit.dev/u/vpol)\
**Post date:** [August 23, 2025, 8:06am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/1 "2025-08-23T08:06:07Z")

</div>

> **[I'm too dumb for Zig's new IO interface](https://www.openmymind.net/Im-Too-Dumb-For-Zigs-New-IO-Interface/)**
>
> Karl Seguin's Blog - A mix of coding and creative writing

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [August 23, 2025, 12:44pm UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/2 "2025-08-23T12:44:37Z")

</div>

The replacement for `read` is `readVec`, it has almost the same behaviour, besides supporting vectored reads and end of stream is now an error. I get why it’s a fundamental operation of the interface, it allows the implementation to optimise vectored reads, I don’t get why there isn’t a `read` wrapper that takes a single buffer.

`reader.interface()` ~~doesn’t make sense because it’s a wrapper for a field access~~ does make sense because the fields change depending on the OS, though they could have just used the same field name, it also does prevent the foot gun of accessing the interface from a temporary that then tries to access the implementation state which doesn’t exist any more because it was temporary.

I also agree that non-optional parameters should not be in an options type.

It’s still a massive improvement, not just in performance, I think the api is better, but it does lack a few simpler functions even if they would just be wrappers over the more complex functions, especially since most of the api is wrapper functions already.

You’re not too dumb for the new api, you just have no knowledge or experience with it, and there isn’t a clear guide to updating.

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [August 25, 2025, 7:39pm UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/3 "2025-08-25T19:39:42Z")

</div>

Me practising…

- Is down here the way to go?
- And… when using directly `File.write()` it is all syscalls without buffering am I right?
- Is there a way to keep track of the number of written bytes?

```zig
// Create
var out_file: std.fs.File = try std.fs.createFileAbsolute("C:/Tmp/testing.txt", .{});
defer out_file.close();

// Use a buffer.
var write_buffer: [1024]u8 = undefined;
var writer = out_file.writer(&write_buffer);

// Get the interface.
const writer_interface: *std.io.Writer = &writer.interface;

// Go.
for (0..42) |i|
{
    try writer_interface.print("writing a line {}", .{ i });
    try writer_interface.writeByte(10); // yeah i know i can use `\n`
}

// Do not forget to flush
try writer_interface.flush();

```

I like the writer interface with all its functions.

One thing I find philosophically unclear (I think I read it in another thread as well):  
This line is not really intuitive.  
`const writer_interface: *std.Io.Writer = &writer.interface;`  
because inside File.Writer it is declared as  
`interface: std.Io.Writer`  
You really have to KNOW how to do it… It is difficult to deduce from the zig-source.

---

<div class="post-metadata">

**Author:** ![pachde](https://ziggit.dev/user_avatar/ziggit.dev/pachde/32/2485_2.png) [@pachde](https://ziggit.dev/u/pachde)\
**Post date:** [August 25, 2025, 8:26pm UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/4 "2025-08-25T20:26:42Z")

</div>

FWIW Re tracking number of bytes written, the 15.1 release notes say:

#### [CountingWriter Deleted](https://ziglang.org/download/0.15.1/release-notes.html#toc-CountingWriter-Deleted)

- If you were discarding the bytes, use `std.Io.Writer.Discarding`, which has a count.
- If you were allocating the bytes, use `std.Io.Writer.Allocating`, since you can check how much was allocated.
- If you were writing to a fixed buffer, use `std.Io.Writer.fixed`, and then check the `end` position.
- Otherwise, try not to create an entire node in the stream graph solely for counting bytes. It’s very disruptive to optimal buffering.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [August 26, 2025, 1:56am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/5 "2025-08-26T01:56:49Z")

</div>

> [@ericlang](#):
>
> This line is not really intuitive.  
> `const writer_interface: *std.Io.Writer = &writer.interface;`  
> because inside File.Writer it is declared as  
> `interface: std.Io.Writer`  
> You really have to KNOW how to do it… It is difficult to deduce from the zig-source

think about it like this, cause this is literally what it is:  
you have a struct you are passing to multiple things, and you want any changes they make to the fields of the struct to be propagated back to you for the other uses of that instance of the struct.

reader/writer have state now, in addition to the implementation state, remember that and you will be fine.

> [@ericlang](#):
>
> Is down here the way to go?

i dont save a pointer to the interface in a variable, I like to see which implementation instance it is referencing when I use it, but yes that code is fine.

> [@ericlang](#):
>
> And… when using directly `File.write()` it is all syscalls without buffering am I right?

yes

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [August 26, 2025, 2:06am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/6 "2025-08-26T02:06:59Z")

</div>

Okidoki thanks.

> [@vulpesx](#):
>
> i dont save a pointer to the interface in a variable

Are you writing all the time something like this?  
`writer.interface.write(...)`

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [August 26, 2025, 2:10am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/7 "2025-08-26T02:10:22Z")

</div>

I meant I don’t do `const writer_interface = &writer.interface;`

Rather, I do `writer.interface.function` and `&writer.interface` when I need to pass it to something.

I find it more explicit, as I can see what writer/reader I am using, rather than an interface pointer that could be pointing to the wrong thing by accident. Names do help, but it’s just one less mistake to make.

---

<div class="post-metadata">

**Author:** ![Cloudef](https://ziggit.dev/user_avatar/ziggit.dev/cloudef/32/1370_2.png) [@Cloudef](https://ziggit.dev/u/Cloudef)\
**Post date:** [August 26, 2025, 2:41am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/8 "2025-08-26T02:41:55Z")

</div>

I’m bit torn on the interface field name as technically struct could implement multiple interfaces.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [August 26, 2025, 3:11am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/9 "2025-08-26T03:11:27Z")

</div>

when it only implements one and the type is named after the interface i dont see any problem using `interface` as a name, also `reader.reader` is generally bad naming.

---

<div class="post-metadata">

**Author:** ![Sze](https://ziggit.dev/user_avatar/ziggit.dev/sze/32/496_2.png) [@Sze](https://ziggit.dev/u/Sze)\
**Post date:** [August 26, 2025, 3:12am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/10 "2025-08-26T03:12:02Z")

</div>

As far as I can tell the fieldname is only used to get the pointer to the Io.Reader or Io.Writer instance and within the virtual methods to get to the implementation via `@fieldParentPtr`, both can be changed (within the impl you would have to replace the virtual table functions with another implementation), if one struct should have multiple.

However in that case you also could consider having them in multiple different structs which are composed instead.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [August 26, 2025, 3:12am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/11 "2025-08-26T03:12:54Z")

</div>

> [@vulpesx](#):
>
> `reader.reader` is generally bad naming

that being said, the context to understand this naming is the same as to understand `interface` so I don’t think It’s that bad.

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [August 26, 2025, 10:19am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/12 "2025-08-26T10:19:04Z")

</div>

Better than `reader.writer` 🙂

---

<div class="post-metadata">

**Author:** ![AndrewKraevskii](https://ziggit.dev/user_avatar/ziggit.dev/andrewkraevskii/32/2564_2.png) [@AndrewKraevskii](https://ziggit.dev/u/AndrewKraevskii)\
**Post date:** [August 26, 2025, 10:29am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/13 "2025-08-26T10:29:49Z")

</div>

reader.writer.source.sink.recieve()

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [August 27, 2025, 6:13am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/14 "2025-08-27T06:13:33Z")

</div>

This does not seem right to me…

```zig
const line = reader.interface.takeDelimiterInclusive('\n') catch break;

```

How can we achieve

```zig
while (readline()) |line|
{
    // process line
}

```

---

<div class="post-metadata">

**Author:** ![tensorush](https://ziggit.dev/letter_avatar_proxy/v4/letter/t/6a8cbe/32.png) [@tensorush](https://ziggit.dev/u/tensorush)\
**Post date:** [August 27, 2025, 6:23am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/15 "2025-08-27T06:23:36Z")

</div>

> **[0.15.1 Release Notes ⚡ The Zig Programming Language](https://ziglang.org/download/0.15.1/release-notes.html#New-stdIoWriter-and-stdIoReader-API)**

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [August 27, 2025, 6:32am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/16 "2025-08-27T06:32:33Z")

</div>

Yes… I tried this `try`, but that is invalid syntax

```zig
while (try reader.takeDelimiterExclusive('\n')) |line| {
}

```

The example is a bit cumbersome.

EDIT:  
Ok this is reasonable at the end.  
`else |err |return err;`

I even didn’t know this was syntactically possible

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [August 27, 2025, 7:58am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/17 "2025-08-27T07:58:47Z")

</div>

Personally I feel`DelimiterError.EndOfStream` should not be an error at all.  
I will look some more around in the interface… It’s all new.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [August 27, 2025, 8:07am UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/18 "2025-08-27T08:07:02Z")

</div>

only `streamDelimiter` errors on end of stream even if there was more than 0 read.

the others dont.

---

<div class="post-metadata">

**Author:** ![pachde](https://ziggit.dev/user_avatar/ziggit.dev/pachde/32/2485_2.png) [@pachde](https://ziggit.dev/u/pachde)\
**Post date:** [August 27, 2025, 4:35pm UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/19 "2025-08-27T16:35:28Z")

</div>

Very common for text files to not have the \n on the last line of the file, so you’d need extra logic for that case.

---

<div class="post-metadata">

**Author:** ![ericlang](https://ziggit.dev/user_avatar/ziggit.dev/ericlang/32/8515_2.png) [@ericlang](https://ziggit.dev/u/ericlang)\
**Post date:** [August 28, 2025, 12:23pm UTC](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645/20 "2025-08-28T12:23:53Z")

</div>

I wrote myself (need it many times for my tests) this little thing for when max linesize is known.

```zig
pub const TextFileReader = struct
{
    allocator: std.mem.Allocator,
    buffer: []u8,
    file: std.fs.File,
    reader: std.fs.File.Reader,

    pub fn init(filename: []const u8, allocator: std.mem.Allocator, linebuffer_size: usize) !TextFileReader
    {
        const file = try std.fs.openFileAbsolute(filename, .{});
        const buf = try allocator.alloc(u8, linebuffer_size);
        return
        .{
            .allocator = allocator,
            .buffer = buf,
            .file = file,
            .reader = file.reader(buf),
        };
    }

    pub fn deinit(self: *TextFileReader) void
    {
        self.allocator.free(self.buffer);
        self.file.close();
    }

    pub fn readline(self: *TextFileReader) !?[]const u8
    {
        const line = self.reader.interface.takeDelimiterExclusive('\n') catch |err|
        {
            return if (err == std.io.Reader.DelimiterError.EndOfStream) null else err;
        };
        return line;
    }
};

```

using it like this

```zig
var tr = try TextFileReader.init(my_filename, my_allocator, 1024);
defer tr.deinit();
while (try tr.readline()) |line|
{
    std.debug.print("{s}\n", .{ line });
}

```

Please shoot at it if you want.

[Next page](https://ziggit.dev/t/im-too-dumb-for-zigs-new-io-interface/11645.md?page=2)
