# fs.File and io.File

**URL:** <https://ziggit.dev/t/fs-file-and-io-file/13362>\
**Category:** Help\
**Tags:** language\
**Created:** [December 4, 2025, 7:48pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362 "2025-12-04T19:48:52Z")\
**Posts on this page:** 13\
**Page:** 1

<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, 7:48pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/1 "2025-12-04T19:48:52Z")

</div>

Where do I find clarity on the future (and current) relationship between [fs.File](https://ziglang.org/documentation/master/std/#std.fs.File) and [io.File](https://ziglang.org/documentation/master/std/#std.Io.File)? The “arrow” is fairly clear, and certain things are stated, like preferring std.Io.Dir.cwd().openFile over the older fs version, but [0.15.1 release notes](https://ziglang.org/download/0.15.1/release-notes.html), despite focusing on Io, also [discusses fs.File.Reader and fs.File.Writer](https://ziglang.org/download/0.15.1/release-notes.html#toc-stdfsFileReader-and-stdfsFileWriter) and how they fulfill the std.Io.Reader and .Writer interfaces. But will they eventually be completely replaced by Io.File? Somehow I’ve missed that clarity, though it’s probably in clear sight.

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

---

<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, 7:50pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/2 "2025-12-04T19:50:23Z")

</div>

Ah.. [maybe this is it](https://ziglang.org/download/0.15.1/release-notes.html#toc-IO-as-an-Interface)

---

<div class="post-metadata">

**Author:** ![andrewrk](https://ziggit.dev/user_avatar/ziggit.dev/andrewrk/32/7011_2.png) [@andrewrk](https://ziggit.dev/u/andrewrk)\
**Post date:** [December 4, 2025, 8:21pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/3 "2025-12-04T20:21:46Z")

</div>

```zig
andy@bark ~/s/zig (std.Io-fs)> gd master --stat
 CMakeLists.txt | 2 -
 build.zig | 35 +-
 lib/compiler/build_runner.zig | 14 +-
 lib/std/Build.zig | 7 +-
 lib/std/Build/Cache.zig | 83 +--
 lib/std/Build/Cache/Directory.zig | 10 +-
 lib/std/Build/Cache/Path.zig | 24 +-
 lib/std/Build/Step.zig | 10 +-
 lib/std/Build/Step/Compile.zig | 35 +-
 lib/std/Build/Step/InstallDir.zig | 7 +-
 lib/std/Build/Step/Options.zig | 42 +-
 lib/std/Io.zig | 34 +-
 lib/std/Io/Dir.zig | 1261 ++++++++++++++++++++++++++++++++++++++++--
 lib/std/Io/File.zig | 628 ++++++++-------------
 lib/std/Io/Threaded.zig | 2006 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 lib/std/debug.zig | 80 +--
 lib/std/fs.zig | 69 +--
 lib/std/fs/AtomicFile.zig | 94 ----
 lib/std/fs/Dir.zig | 2066 ---------------------------------------------------------------------
 lib/std/fs/File.zig | 1050 +----------------------------------
 lib/std/fs/test.zig | 15 -
 lib/std/os.zig | 130 -----
 lib/std/posix.zig | 1184 ++-------------------------------------
 lib/std/std.zig | 3 +
 lib/std/zig/system.zig | 6 +-
 25 files changed, 3585 insertions(+), 5310 deletions(-)

```

`std.fs.File` and `std.fs.Dir` will be deleted soon

---

<div class="post-metadata">

**Author:** ![squeek502](https://ziggit.dev/user_avatar/ziggit.dev/squeek502/32/409_2.png) [@squeek502](https://ziggit.dev/u/squeek502)\
**Post date:** [December 5, 2025, 12:53am UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/4 "2025-12-05T00:53:04Z")

</div>

To clarify, `std.Io.File` doesn’t exist in 0.15.x.

In the master branch, the `fs.File` → `Io.File` transition is a work-in-progress, and was initially introduced in

> <https://github.com/ziglang/zig/pull/25592>
>
> This patchset adds \`std.Io\` and provides two implementations for it:
> 
> \* std.Io….Threaded - based on a thread pool.
> - \`-fno-single-threaded\` - supports concurrency and cancellation.
> - \`-fsingle-threaded\` - does not support concurrency or cancellation.
> \* std.Io.Evented - work-in-progress, experimental. This API is not ready to be used yet, but it serves to inform the evolution of \`std.Io\` API.
> - IoUring implementation for Linux proof of concept. This backend has really nice properties but it's not finished yet.
> - KQueue implementation, implemented enough to prove the concept, including fixing \[a common bug in other async runtimes\](https://github.com/mitchellh/libxev/issues/125).
> 
> \`std.Io.Threaded\` has networking and file-system operations implemented.
> Cancellation works beautifully, except for a known race condition that has a
> couple of competing solutions already in mind.
> 
> All of \`std.net\` has been deleted in favor of \`std.Io.net\`.
> 
> \`std.fs\` has been partially updated to use \`std.Io\` - only as required so that
> \`std.Io.Writer.sendFile\` could use \`\*std.Io.File.Reader\` rather than
> \`\*std.fs.File.Reader\`.
> 
> closes https://github.com/ziglang/zig/issues/8224
> 
> \## Laundry List of Io Features
> 
> \* \`async\`/\`await\` - these primitives express that operations can be done
> independently, making them infallible and support execution on limited Io
> implementations that lack a concurrency mechanism.
> \* \`concurrent\` - same as \`async\` except communicates that the operation
> \*must\* be done concurrently for correctness. Requires memory allocation.
> \* \`cancel\` - equivalent to \`await\` except also requests the Io implementation
> to interrupt the operation and return \`error.Canceled\`. \`std.Io.Threaded\`
> supports cancellation by sending a signal to a thread, causing blocking
> syscalls to return \`EINTR\`, giving a chance to notice the cancellation request.
> \* \`select\` - API for blocking on multiple futures using \`switch\` syntax
> \* \`Group\` - efficiently manages many async tasks. Supports waiting for and
> cancelling all tasks in the group together.
> \* \`Queue(T)\` - Many producer, many consumer, thread-safe, runtime configurable
> buffer size. When buffer is empty, consumers suspend and are resumed by
> producers. When buffer is full, producers suspend and are resumed by consumers.
> - Avoids code bloat using a type safe wrapper around \`TypeErasedQueue\`.
> \* \`Select\` - for blocking on runtime-known number of tasks and handling a
> subset of them.
> \* \`Clock\`, \`Duration\`, \`Timestamp\`, \`Timeout\` - type safety for units of measurement
> \* \`Mutex\`, \`Condition\` - synchronization primitives
> 
> \## Demo
> 
> Here is an example that makes an HTTP request to a domain:
> 
> \`\`\`zig
> const std = @import("std");
> const assert = std.debug.assert;
> const Allocator = std.mem.Allocator;
> const Io = std.Io;
> const HostName = std.Io.net.HostName;
> 
> pub fn main() !void {
> var debug\_allocator: std.heap.DebugAllocator(.{}) = .init;
> const gpa = debug\_allocator.allocator();
> 
> var threaded: std.Io.Threaded = .init(gpa);
> defer threaded.deinit();
> 
> const io = threaded.io();
> 
> const args = try std.process.argsAlloc(gpa);
> 
> const host\_name: HostName = try .init(args\[1\]);
> 
> var http\_client: std.http.Client = .{ .allocator = gpa, .io = io };
> defer http\_client.deinit();
> 
> var request = try http\_client.request(.HEAD, .{
> .scheme = "http",
> .host = .{ .percent\_encoded = host\_name.bytes },
> .port = 80,
> .path = .{ .percent\_encoded = "/" },
> }, .{});
> defer request.deinit();
> 
> try request.sendBodiless();
> 
> var redirect\_buffer: \[1024\]u8 = undefined;
> var response = try request.receiveHead(&redirect\_buffer);
> std.log.info("received {d} {s}", .{ response.head.status, response.head.reason });
> }
> \`\`\`
> 
> Thanks to the fact that networking is now taking advantage of the new \`std.Io\` interface,
> this code has the following properties:
> \* It asynchronously sends out DNS queries to each configured nameserver
> \* As each response comes in, it immediately, asynchronously tries to TCP connect to the
> returned IP address.
> \* Upon the first successful TCP connection, all other in-flight connection
> attempts are canceled, including DNS queries.
> \* The code also works when compiled with \`-fsingle-threaded\` even though the
> operations happen sequentially.
> \* No heap allocation.
> 
> You can see how this is implemented in \`std.Io.net.HostName.connect\`:
> 
> \`\`\`zig
> pub fn connect(
> host\_name: HostName,
> io: Io,
> port: u16,
> options: IpAddress.ConnectOptions,
> ) ConnectError!Stream {
> var connect\_many\_buffer: \[32\]ConnectManyResult = undefined;
> var connect\_many\_queue: Io.Queue(ConnectManyResult) = .init(&connect\_many\_buffer);
> 
> var connect\_many = io.async(connectMany, .{ host\_name, io, port, &connect\_many\_queue, options });
> var saw\_end = false;
> defer {
> connect\_many.cancel(io);
> if (!saw\_end) while (true) switch (connect\_many\_queue.getOneUncancelable(io)) {
> .connection =\> |loser| if (loser) |s| s.closeConst(io) else |\_| continue,
> .end =\> break,
> };
> }
> 
> var aggregate\_error: ConnectError = error.UnknownHostName;
> 
> while (connect\_many\_queue.getOne(io)) |result| switch (result) {
> .connection =\> |connection| if (connection) |stream| return stream else |err| switch (err) {
> error.SystemResources,
> error.OptionUnsupported,
> error.ProcessFdQuotaExceeded,
> error.SystemFdQuotaExceeded,
> error.Canceled,
> =\> |e| return e,
> 
> error.WouldBlock =\> return error.Unexpected,
> 
> else =\> |e| aggregate\_error = e,
> },
> .end =\> |end| {
> saw\_end = true;
> try end;
> return aggregate\_error;
> },
> } else |err| switch (err) {
> error.Canceled =\> |e| return e,
> }
> }
> 
> pub const ConnectManyResult = union(enum) {
> connection: IpAddress.ConnectError!Stream,
> end: ConnectError!void,
> };
> 
> /// Asynchronously establishes a connection to all IP addresses associated with
> /// a host name, adding them to a results queue upon completion.
> pub fn connectMany(
> host\_name: HostName,
> io: Io,
> port: u16,
> results: \*Io.Queue(ConnectManyResult),
> options: IpAddress.ConnectOptions,
> ) void {
> var canonical\_name\_buffer: \[max\_len\]u8 = undefined;
> var lookup\_buffer: \[32\]HostName.LookupResult = undefined;
> var lookup\_queue: Io.Queue(LookupResult) = .init(&lookup\_buffer);
> 
> host\_name.lookup(io, &lookup\_queue, .{
> .port = port,
> .canonical\_name\_buffer = &canonical\_name\_buffer,
> });
> 
> var group: Io.Group = .init;
> 
> while (lookup\_queue.getOne(io)) |dns\_result| switch (dns\_result) {
> .address =\> |address| group.async(io, enqueueConnection, .{ address, io, results, options }),
> .canonical\_name =\> continue,
> .end =\> |lookup\_result| {
> group.waitUncancelable(io);
> results.putOneUncancelable(io, .{ .end = lookup\_result });
> return;
> },
> } else |err| switch (err) {
> error.Canceled =\> |e| {
> group.cancel(io);
> results.putOneUncancelable(io, .{ .end = e });
> },
> }
> }
> \`\`\`
> 
> \## Upgrade Guide
> 
> \### Missing \`io\` Parameter
> 
> If you need an \`io\` parameter, and you don't have one, you can get one like this:
> 
> \`\`\`zig
> var threaded: Io.Threaded = .init\_single\_threaded;
> const io = threaded.io();
> \`\`\`
> 
> This is legal as long as these functions are not called:
> \* \`Io.VTable.concurrent\`
> 
> This is a non-ideal workaround - like reaching for \`std.heap.page\_allocator\` when
> you need an \`Allocator\` and do not have one. Instead, it is better to accept an
> \`Io\` parameter if you need one (or store one on a context struct for convenience).
> Point is that the application's \`main\` function should generally be responsible for
> constructing the \`Io\` instance used throughout.
> 
> When you're testing you can use \`std.testing.io\` (much like \`std.testing.allocator\`).
> 
> \### How to use async/await
> 
> Use this pattern to avoid resource leaks and handle cancellation gracefully:
> 
> \`\`\`zig
> var foo\_future = io.async(foo, .{args});
> defer if (foo\_future.cancel(io)) |resource| resource.deinit() else |\_| {}
> 
> var bar\_future = io.async(bar, .{args});
> defer if (bar\_future.cancel(io)) |resource| resource.deinit() else |\_| {}
> 
> const foo\_result = try foo\_future.await(io);
> const bar\_result = try bar\_future.await(io);
> \`\`\`
> 
> If the \`foo\` or \`bar\` function does not return a resource that must be freed, then the \`if\` can be simplified to \`\_ = foo() catch {}\`, and if the function returns \`void\`, then the discard can also be removed. The \`cancel\` is necessary however because it releases the async task resource when errors (including \`error.Canceled\`) are returned.
> 
> \## Related
> \* https://github.com/ziglang/zig/issues/24510
> \* https://github.com/ziglang/zig/issues/23367
> \* https://github.com/ziglang/zig/issues/23446
> \* https://github.com/ziglang/zig/issues/1639
> \* https://github.com/ziglang/zig/issues/157
> 
> \## Followup Issues
> \* https://github.com/ziglang/zig/issues/25738
> \* https://github.com/ziglang/zig/issues/25739
> \* https://github.com/ziglang/zig/issues/25740
> \* https://github.com/ziglang/zig/issues/25741
> \* https://github.com/ziglang/zig/issues/25742
> \* https://github.com/ziglang/zig/issues/25743
> \* https://github.com/ziglang/zig/issues/25744
> \* https://github.com/ziglang/zig/issues/25745
> \* https://github.com/ziglang/zig/issues/25746
> \* https://github.com/ziglang/zig/issues/25747
> \* https://github.com/ziglang/zig/issues/25748
> \* https://github.com/ziglang/zig/issues/25749
> \* https://github.com/ziglang/zig/issues/25750
> \* https://github.com/ziglang/zig/issues/25751
> \* https://github.com/ziglang/zig/issues/25752
> \* https://github.com/ziglang/zig/issues/25753
> \* https://github.com/ziglang/zig/issues/6600
> \* https://github.com/ziglang/zig/issues/14173
> \* https://github.com/ziglang/zig/issues/25754
> \* https://github.com/ziglang/zig/issues/25755
> \* https://github.com/ziglang/zig/issues/25756
> \* https://github.com/ziglang/zig/issues/25757
> \* https://github.com/ziglang/zig/issues/25760
> \* https://github.com/ziglang/zig/issues/25762
> \* add non-blocking flag to net and fs operations, handle EAGAIN
> \* Threaded: in netLookup, potentially add diagnostics to the output in several places

Relevant follow-up issue:

> <https://github.com/ziglang/zig/issues/25738>
>
> Extracted from https://github.com/ziglang/zig/pull/25592.
> 
> All of std.fs, std.fs….File, and std.fs.Dir should be deprecated and implementations moved to std.Io.File and std.Io.Dir.
> 
> To close the issue std.Io.Threaded must implement all the APIs, including these which currently have \`@panic\` stubs:
> \* dirStat
> \* fileSeekBy
> \* dirMakePath
> \* openSelfExe
> \* fileWritePositional
> \* fileWriteStreaming

---

<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 5, 2025, 4:05am UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/5 "2025-12-05T04:05:26Z")

</div>

Ha! Now **that’s** horizontal-scrollbar-clear!

In this case, though there are some snippets in the release notes and elsewhere, I’d like critique on a couple of possible basic usage examples for reading a text file line-by-line assuming \n delimiters:

First, an allocator-less one that requires a big-enough buffer:

```zig
   const io = std.testing.io;
   var file = try std.Io.Dir.cwd().openFile(io, "test.txt", .{});
   defer file.close(io);
 
   var buf: [1024]u8 = undefined; // must be big enough for an entire line
   var reader: std.Io.File.Reader = file.reader(io, &buf);
   while(reader.interface.takeDelimiter('\n')) |line| {
      if (line) |l| { std.debug.print("line: {s}\n", .{l}); }
      else { break; }
   } else |err| return err;

```

Second, using Writer.Allocating:

```zig
   const io = std.testing.io;
   var file = try std.Io.Dir.cwd().openFile(io, "test.txt", .{});
   defer file.close(io);

   var gpa = std.heap.DebugAllocator(.{}){};
   defer _ = gpa.deinit();
   const alloc = gpa.allocator();

   var line = std.Io.Writer.Allocating.init(alloc);
   defer line.deinit();
   var buf: [1]u8 = undefined; // ridiculously small for illustration
   var reader: std.Io.File.Reader = file.reader(io, &buf);
   while(reader.interface.streamDelimiter(&line.writer, '\n')) |count| {
      _ = count;
      _ = reader.interface.toss(1);
      std.debug.print("line: {s}\n", .{line.written()});
      line.clearRetainingCapacity();
   } else |err| switch (err) {
      error.EndOfStream => {}, // while loop done
      error.WriteFailed, error.ReadFailed => |e| return e,
   }
   if (line.written().len > 0) {
      std.debug.print("tail: {s}\n", .{line.written()});
   }

```

Please criticize without reservation. I’m not suggesting it’s a model.

---

<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, 5:24am UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/6 "2025-12-05T05:24:40Z")

</div>

Those are good approaches. And the only additional case is to handle any remaining data beyond the last delimiter. There can be some pathological files ending with non-LF characters.

See the last part in this [blog](https://williamw520.github.io/2025/09/23/back-to-basic-reading-file-in-zig.html).

---

<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 5, 2025, 2:56pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/7 "2025-12-05T14:56:25Z")

</div>

Ah, right, of course, the poor puppy’s tail. I’ll glue it back on….

---

<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 5, 2025, 4:14pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/8 "2025-12-05T16:14:44Z")

</div>

Ok, I fixed the above.

Adding a tail processor to the Allocating variant was straightforward enough, but the stack-based variant proved more interesting. It contracted substantially by making use of `takeDelimiter` rather than `takeDelimiterInclusive` - in this case, the ‘line’ is returned even if EOF is encountered (EOF is treated like the delimiter case). This simplifies the else |err| handling, as all remaining errors are real errors, and it might be commonplace to just pass them up.

This inspired two splinters.

First: should the documentation for `takeDelimiterInclusive`, which says “advancing the seek position past the delimiter,” say, instead, “advancing the seek position past the delimiter, unless an error is encountered”? as the code does seem to suggest that the seek position is not advanced if a peek() fails to find the delimiter before encountering EOF. Then, another (more verbose) way of dealing with this would be to keep my `takeDelimiterInclusive` code, but add tail-processing code below, as done with the heap version.

Second: I consulted [this documentation](https://ziglang.org/documentation/master/#while-with-Optionals) and [this documentation](https://ziglang.org/documentation/master/#Optionals) on optionals, and didn’t see detail on !?T handling. Elsewhere it’s (erroneously?) suggested that the while loop _breaks_ when an optional conditional is null, but in this interesting case with else |err| handling, I found, here, at least, that |line| simply remains an optional, so, as you see, I have to if-unwrap it to its raw string value within the while braces. This compiles, so I assume it’s right, but, again, I’m curious about the documentation.. maybe it wants to gain a short paragraph about this double-whammy scenario.

(Third?!: I suppose I feel like making one more proposal/question: why not call takeDelimiter (and variants) takeToDelimiter(), since that’s what’s actually being done. I wonder if, since this is fresh on the press, it would be a good time to do that now, early?)

For the record, since I modified the stack case substantially, above, here’s the old inferior bit:

```zig
   // INFERIOR / discarded version:
   while(reader.interface.takeDelimiterInclusive('\n')) |line| {
      std.debug.print("line: {s}\n", .{line});
   } else |err| switch (err) {
      error.EndOfStream => {}, // while loop done
      error.StreamTooLong, error.ReadFailed => |e| return e,
   } // note: there's still a tail to process!!

```

---

<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 5, 2025, 4:35pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/9 "2025-12-05T16:35:45Z")

</div>

I’m also interested in more followup advice on the reader-to-writer pattern, with my:

> [@jmctagger](#):
>
> `var buf: [1]u8 = undefined; // ridiculously small for illustration`

Should documentation offer some guidance on that Reader buf, in the event of direct-pathing the read-to-write stream, where there’s a heap-allocated buffer to receive data?

---

<div class="post-metadata">

**Author:** ![ant0me](https://ziggit.dev/user_avatar/ziggit.dev/ant0me/32/7110_2.png) [@ant0me](https://ziggit.dev/u/ant0me)\
**Post date:** [December 5, 2025, 11:36pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/10 "2025-12-05T23:36:59Z")

</div>

Thanks for sharing. I enjoyed reading the series of blog posts!!

---

<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 26, 2025, 4:34am UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/11 "2025-12-26T04:34:47Z")

</div>

Finally, I’ve collected the takeaways into an article

[https://codeberg.org/jmcaine/zig-notes/src/branch/main/file-io.md](https://codeberg.org/jmcaine/zig-notes/src/branch/main/file-io.md)

This is my first of the sort, and stems from not finding something like it. Please critique liberally (I’d be honored). I reference official doc and std doc heavily, and cite (and thank) [pedropark99’s zig book, chapter 12](https://williamw520.github.io/2025/09/23/back-to-basic-reading-file-in-zig.html) and [williamw520’s I/O “basics” article](https://pedropark99.github.io/zig-book/Chapters/12-file-op.html), though both of those currently reference 0.15 rather than 0.16.

---

<div class="post-metadata">

**Author:** ![anon47052109](https://ziggit.dev/letter_avatar_proxy/v4/letter/a/71c47a/32.png) [@anon47052109](https://ziggit.dev/u/anon47052109)\
**Post date:** [December 26, 2025, 12:35pm UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/12 "2025-12-26T12:35:30Z")

</div>

~~ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ~~

---

<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 27, 2025, 3:44am UTC](https://ziggit.dev/t/fs-file-and-io-file/13362/13 "2025-12-27T03:44:25Z")

</div>

> [@anon47052109](#):
>
> - On the section “Reading a whole file”, just to mention that the result type of `std.Io.Dir.readFile` is a slice (pointer) to buf and not a new allocated thing

Indeed, this is the kind of thing that becomes “natural” too quickly, and is still foreign to noobs; I shall add a clarifying phrase.

> [@anon47052109](#):
>
> When one decides to allocate a buffer, …

Right… I’ll consider adding a little more, without detouring too much, but think at least a reference to Allocator documentation is in order. Thanks.
