# Back to Basics: File Reading with the New IO in Zig

**URL:** <https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180>\
**Category:** Media\
**Tags:** standard-library\
**Created:** [September 24, 2025, 1:08am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180 "2025-09-24T01:08:29Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [September 24, 2025, 1:08am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/1 "2025-09-24T01:08:29Z")

</div>

> **[Back to Basics: File Reading with the New IO in Zig](https://williamw520.github.io/2025/09/23/back-to-basic-reading-file-in-zig.html)**
>
> Embracing the New Input/Output API in Zig 0.15.1

---

<div class="post-metadata">

**Author:** ![Zambyte](https://ziggit.dev/user_avatar/ziggit.dev/zambyte/32/4977_2.png) [@Zambyte](https://ziggit.dev/u/Zambyte)\
**Post date:** [September 24, 2025, 2:47am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/2 "2025-09-24T02:47:05Z")

</div>

Small note but `GeneralPurposeAllocator` was renamed to `DebugAllocator` [in 0.14.0 🙂](https://ziglang.org/download/0.14.0/release-notes.html#DebugAllocator)

---

<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:** [September 24, 2025, 4:22am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/3 "2025-09-24T04:22:04Z")

</div>

I’m not sure `reader.toss(1)` is the right API for discarding the delimiter byte, have you tried using `takeDelimiterExclusive`?

> **[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:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [September 24, 2025, 4:37am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/4 "2025-09-24T04:37:56Z")

</div>

Ha. Copying and pasting couple month old code are outdated already. Thanks for the info! I’ve updated the blog with `DebugAllocator`.

---

<div class="post-metadata">

**Author:** ![plaukiu](https://ziggit.dev/user_avatar/ziggit.dev/plaukiu/32/723_2.png) [@plaukiu](https://ziggit.dev/u/plaukiu)\
**Post date:** [September 24, 2025, 4:46am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/5 "2025-09-24T04:46:44Z")

</div>

careful [std.Io.Reader: fix delimiter bugs by mlugg · Pull Request #25169 · ziglang/zig · GitHub](https://github.com/ziglang/zig/pull/25169)

---

<div class="post-metadata">

**Author:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [September 24, 2025, 4:54am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/6 "2025-09-24T04:54:33Z")

</div>

Good question. The current implementation of [`takeDelimiterExclusive`](https://github.com/ziglang/zig/blob/0.15.1/lib/std/Io/Reader.zig#L803) only looks in the buffered data up to the buffer’s capacity for the delimiter. It won’t look beyond that. See the called [peekDelimiterInclusive](https://github.com/ziglang/zig/blob/0.15.1/lib/std/Io/Reader.zig#L771) doing the read; the while loop only ranges over the buffer’s capacity.

To make it work, you have to have a big enough buffer to read in the data with the delimiter. That’s too much assumption for a general purpose read-until-delimiter.

I’ve gone through all variations of the delimiter reading API and settled on the `streamDelimiter()` with a growing writer buffer.

---

<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:** [September 24, 2025, 3:19pm UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/7 "2025-09-24T15:19:37Z")

</div>

In provided snippet:

```zig
var reader = &file_reader.interface;

```

`reader` variable can be made const since it’s a pointer and not a value. Compiler sadly can’t detect it ☹

---

<div class="post-metadata">

**Author:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [September 24, 2025, 3:56pm UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/8 "2025-09-24T15:56:29Z")

</div>

I remember seeing a compile error: expected type ‘\*Io.Reader’, found ‘\*const Io.Reader’ at reader.streamDelimiter(…) before and changed it to var. Now that I change it back to const as you suggest and it works. I guess it was the ‘const file\_reader’ causing it and it showed up as a compile error on ‘reader’. Thanks for the info.

---

<div class="post-metadata">

**Author:** ![wjlewis](https://ziggit.dev/user_avatar/ziggit.dev/wjlewis/32/5911_2.png) [@wjlewis](https://ziggit.dev/u/wjlewis)\
**Post date:** [December 4, 2025, 3:18pm UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/9 "2025-12-04T15:18:19Z")

</div>

Thanks for writing and sharing this! It helped me a lot.

If I want to read the entire contents of a file (instead of line-by-line), is this the best approach:

1. Create an `ArrayList(u8)` in which to store the contents.
2. Call `appendRemaining` on the reader, passing a suitable allocator and the array list.

?

Specifically:

```zig
// --snip--
const reader = &file_reader.interface;
var text_buf = std.ArrayList(u8).empty;
// Read the entire file into `text_buf`.
try reader.appendRemaining(alloc, &text_buf, .unlimited);

// Get a slice to the text content. Alternatively, call `toOwnedSlice()`.
const text = text_buf.items;

```

---

<div class="post-metadata">

**Author:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [December 4, 2025, 3:53pm UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/10 "2025-12-04T15:53:07Z")

</div>

Thanks for the comment. For reading the entire file into memory, using appendRemaining works when loading into an ArrayList. There’re several other ways. The following are some examples.

This is an one-liner, simplest.

> <https://github.com/williamw520/misc_zig/blob/main/read_full1.zig>

This one let you allocate a buffer first.

> <https://github.com/williamw520/misc_zig/blob/main/read_full2.zig>

This one is most efficient, least amount of buffer copying.

> <https://github.com/williamw520/misc_zig/blob/main/read_full3.zig>

This one is for when the file size is unknown, reading as much as possible until the EOF.

> <https://github.com/williamw520/misc_zig/blob/main/read_full4.zig>

Cheers.

---

<div class="post-metadata">

**Author:** ![jmctagger](https://ziggit.dev/letter_avatar_proxy/v4/letter/j/8dc957/32.png) [@jmctagger](https://ziggit.dev/u/jmctagger)\
**Post date:** [December 4, 2025, 6:16pm UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/11 "2025-12-04T18:16:33Z")

</div>

Funny that this should resurface today.

Earlier, [stratts advocated](https://ziggit.dev/t/struggling-to-simply-read-from-a-file/13337/15) for _not_ assigning the interface to a variable at all.

Indeed, in this case, in [your post](https://williamw520.github.io/2025/09/23/back-to-basic-reading-file-in-zig.html):

```zig
    var file_reader: std.fs.File.Reader = file.reader(&read_buf);

    // Pointer to the std.Io.Reader interface to use the generic IO functions.
    const reader = &file_reader.interface;

```

You create a reader just as in the discussion (and oops) of that thread. I notice that file\_reader is used for nothing other than its interface, and that the interface (`reader`) is used only twice. Obviously, in a real app, it would potentially be used much more, and `file_reader` might, too, but would it make sense to propagate @stratts proposal, and just

```zig
    _ = file_reader.interface.streamDelimiter(&line...

```

instead, and possibly rename variables for ergonomics?

---

<div class="post-metadata">

**Author:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [December 4, 2025, 7:17pm UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/12 "2025-12-04T19:17:37Z")

</div>

stratts and you have a good point.

---

<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:** [December 5, 2025, 12:27am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/13 "2025-12-05T00:27:26Z")

</div>

> [@ww520](#):
>
> This one is most efficient, least amount of buffer copying.

There should be no buffer copying in half of your solutions.  
The reader will first copy data from its buffer to the output buffer/writer _if it has any data_, then it reads the data directly into the output buffer/writer. In your examples, you don’t do any operation that puts data in the internal buffer, _Excluding the example where you only fill the buffer_.

_Except `Writer.Allocating`, as that might copy data when growing its buffer  
Also except `Dir.readFileAlloc` as that appends to an array list_

---

<div class="post-metadata">

**Author:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [December 5, 2025, 12:59am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/14 "2025-12-05T00:59:44Z")

</div>

Trace through the call stack of `std.fs.Dir.readFileAlloc`.

---

<div class="post-metadata">

**Author:** ![wjlewis](https://ziggit.dev/user_avatar/ziggit.dev/wjlewis/32/5911_2.png) [@wjlewis](https://ziggit.dev/u/wjlewis)\
**Post date:** [December 5, 2025, 2:12am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/15 "2025-12-05T02:12:03Z")

</div>

Thank you again! That’s much clearer that what I was doing. 👍

---

<div class="post-metadata">

**Author:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [December 6, 2025, 12:54am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/16 "2025-12-06T00:54:10Z")

</div>

There seem to be quite a bit of questions regarding file I/O. I’ve put together two more blog posts diving into the topic.

> **[Back to Basics: Reading Files Fully in Zig](https://williamw520.github.io/2025/12/04/back-to-basic-read-file-fully.html)**
>
> Ways to Read a File Fully with the New IO in Zig

> **[Back to Basics: Reading a File into Lines in Zig](https://williamw520.github.io/2025/12/05/back-to-basic-read-file-lines.html)**
>
> Building a Simple API for Reading Lines of a File in Zig

Most languages have an one-liner library function to read a text file into lines. The above is a version for Zig. It supports a simple usage like below.

```zig
var f = try FileLines.read(alloc, std.fs.cwd(), "test.txt");
for (f.lines()) |line| {
    std.debug.print("line = '{s}'\n", .{line});
}
f.deinit();

```

---

<div class="post-metadata">

**Author:** ![mg979](https://ziggit.dev/letter_avatar_proxy/v4/letter/m/3e96dc/32.png) [@mg979](https://ziggit.dev/u/mg979)\
**Post date:** [December 6, 2025, 2:19am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/17 "2025-12-06T02:19:20Z")

</div>

Thank you for this, any idea about how the best way to make partial reads/writes? For example I could stop reading after N lines, then read the rest by setting position with `file.seekTo()`, but then on write, how can you write only a part of the file, assuming you didn’t read it all and edited a part of it?

---

<div class="post-metadata">

**Author:** ![npc1054657282](https://ziggit.dev/user_avatar/ziggit.dev/npc1054657282/32/5790_2.png) [@npc1054657282](https://ziggit.dev/u/npc1054657282)\
**Post date:** [December 6, 2025, 2:24am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/18 "2025-12-06T02:24:26Z")

</div>

I think `streamDelimiter` should be helpful.

---

<div class="post-metadata">

**Author:** ![ww520](https://ziggit.dev/letter_avatar_proxy/v4/letter/w/c2a13f/32.png) [@ww520](https://ziggit.dev/u/ww520)\
**Post date:** [December 6, 2025, 10:50am UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/19 "2025-12-06T10:50:40Z")

</div>

std.fs.File.Reader.seekTo() should work with partial reading with jumping to different parts of the file. It will adjust the associated std.Io.Reader interface for the seek operation.

std.fs.File.Writer.seekTo() also should work. It will adjust the associated std.Io.Writer interface for the seek operation. You just seek to a position in the file and write the number of bytes for the partial data.

Also the File.Reader and File.Writer are opened with the .positional mode by default.

---

<div class="post-metadata">

**Author:** ![mg979](https://ziggit.dev/letter_avatar_proxy/v4/letter/m/3e96dc/32.png) [@mg979](https://ziggit.dev/u/mg979)\
**Post date:** [December 6, 2025, 1:11pm UTC](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180/20 "2025-12-06T13:11:37Z")

</div>

> [@ww520](#):
>
> std.fs.File.Writer.seekTo() also should work

Thanks, I’ll try with this one.

[Next page](https://ziggit.dev/t/back-to-basics-file-reading-with-the-new-io-in-zig/12180.md?page=2)
