7.3 KiB
File System API Reference (Zig 0.16.0)
Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html
Zig 0.16 migrates file-system operations to std.Io. Use std.Io.Dir, std.Io.File, and an explicit std.Io parameter. std.fs is now mostly path helpers and deprecated compatibility names.
The snippets below are focused fragments. They assume const std = @import("std"), a caller-supplied io: std.Io, a suitable allocator, and any
named application values such as max_size, from, and to.
Core Types
std.Io.Dir // Directory handle and file-system operations
std.Io.File // File handle, readers, writers, stat, locking
std.Io.File.Atomic
std.Io.File.MemoryMap
std.Io.Dir.path // Path helpers; std.fs.path is deprecated compatibility
Current Directory
const cwd = std.Io.Dir.cwd();
Do not use old std.fs.cwd() in new 0.16 code.
Opening Files
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
defer file.close(io);
const rw = try std.Io.Dir.cwd().openFile(io, "data.txt", .{
.mode = .read_write,
});
defer rw.close(io);
Creating Files
const file = try std.Io.Dir.cwd().createFile(io, "out.txt", .{});
defer file.close(io);
With default options, createFile truncates an existing regular file. Use the
exclusive-create option when overwriting must fail. Other options cover read
access, permissions, and locking where supported. File access mode is an
openFile option, not the name of the creation-permissions field.
Reading Files
Stream With Reader
const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{});
defer file.close(io);
var buffer: [4096]u8 = undefined;
var file_reader = file.reader(io, &buffer);
const reader = &file_reader.interface;
while (try reader.takeDelimiter('\n')) |line| {
_ = line;
}
Allocate Whole File
const bytes = try std.Io.Dir.cwd().readFileAlloc(io, "data.txt", allocator, .limited(1024 * 1024));
defer allocator.free(bytes);
The limit uses std.Io.Limit. Hitting the limit returns error.StreamTooLong.
readFileAlloc creates a file reader internally and reads until the supplied
limit. Use an explicit File.Reader when streaming, reusing buffers, or
controlling incremental consumption.
Read To End From Existing File
var buffer: [4096]u8 = undefined;
var file_reader = file.reader(io, &buffer);
const bytes = try file_reader.interface.allocRemaining(allocator, .limited(max_size));
defer allocator.free(bytes);
Writing Files
Stream With Writer
const file = try std.Io.Dir.cwd().createFile(io, "out.txt", .{});
defer file.close(io);
var buffer: [4096]u8 = undefined;
var file_writer = file.writer(io, &buffer);
const writer = &file_writer.interface;
try writer.print("value={d}\n", .{42});
try writer.writeAll("raw bytes\n");
try writer.flush();
Write Whole File
try std.Io.Dir.cwd().writeFile(io, .{
.sub_path = "out.txt",
.data = "hello\n",
});
Standard I/O
var stdout_buffer: [4096]u8 = undefined;
var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer);
try stdout_writer.interface.print("hello\n", .{});
try stdout_writer.interface.flush();
var stderr_buffer: [4096]u8 = undefined;
var stderr_writer = std.Io.File.stderr().writer(io, &stderr_buffer);
try stderr_writer.interface.print("error: {s}\n", .{"message"});
try stderr_writer.interface.flush();
var stdin_buffer: [4096]u8 = undefined;
var stdin_reader = std.Io.File.stdin().reader(io, &stdin_buffer);
if (try stdin_reader.interface.takeDelimiter('\n')) |line| {
_ = line;
}
Directories
var dir = try std.Io.Dir.cwd().openDir(io, "assets", .{ .iterate = true });
defer dir.close(io);
try std.Io.Dir.cwd().createDir(io, "new-dir", .default_dir);
try std.Io.Dir.cwd().createDirPath(io, "path/to/nested");
Both walking APIs require a directory opened with .iterate = true; iterating
without that capability is illegal behavior. Use other openDir options for
access/no-follow behavior as needed.
Walking
Use walk for full recursive walks and walkSelectively when you want to decide which directories to enter.
var walker = try dir.walk(allocator);
defer walker.deinit();
while (try walker.next(io)) |entry| {
_ = entry;
}
var walker = try dir.walkSelectively(allocator);
defer walker.deinit();
while (try walker.next(io)) |entry| {
if (entry.kind == .directory and shouldEnter(entry)) {
try walker.enter(io, entry);
}
}
Metadata
const stat = try file.stat(io);
const size = stat.size;
const kind = stat.kind;
const modified = stat.mtime;
const accessed = stat.atime orelse return error.FileAccessTimeUnavailable;
_ = .{ size, kind, modified, accessed };
atime is optional in 0.16.
Setting timestamps uses structured options:
try file.setTimestamps(io, .{
.access_timestamp = .init(stat.atime),
.modify_timestamp = .init(stat.mtime),
});
Paths
std.Io.Dir.path / std.fs.path functions handle Windows paths more consistently in 0.16.
The relative-path helpers are pure and receive the current directory rather
than querying the OS. relative and relativeWindows also accept an optional
environment map for Windows per-drive current-directory resolution;
relativePosix has a distinct signature without that map.
const cwd_path = try std.process.currentPathAlloc(io, allocator);
defer allocator.free(cwd_path);
const rel = try std.fs.path.relative(allocator, cwd_path, environ_map, from, to);
defer allocator.free(rel);
Atomic Files
Obtain a std.Io.File.Atomic value through Dir.createFileAtomic, then use its
public cleanup/materialization lifecycle. Do not construct File.Atomic
directly or hand-roll random temporary names; the helper routes entropy and
filesystem work through std.Io.
Absolute Operations
Many old std.fs.*Absolute functions moved to std.Io.Dir.*Absolute, for example:
std.fs.openFileAbsolute->std.Io.Dir.openFileAbsolutestd.fs.createFileAbsolute->std.Io.Dir.createFileAbsolutestd.fs.deleteFileAbsolute->std.Io.Dir.deleteFileAbsolutestd.fs.renameAbsolute->std.Io.Dir.renameAbsolutestd.fs.accessAbsolute->std.Io.Dir.accessAbsolute
Many Z and W path-specific helpers were removed.
Migration Map
| Old 0.15-style API | Zig 0.16 API |
|---|---|
std.fs.cwd() |
std.Io.Dir.cwd() |
std.fs.File.stdout() |
std.Io.File.stdout() |
file.close() |
file.close(io) |
dir.close() |
dir.close(io) |
file.reader(&buf) |
file.reader(io, &buf) |
file.writer(&buf) |
file.writer(io, &buf) |
dir.readFileAlloc(allocator, path, max) |
dir.readFileAlloc(io, path, allocator, .limited(max)) |
file.readToEndAlloc(allocator, max) |
file.reader(io, &buf).interface.allocRemaining(allocator, .limited(max)) |
std.process.getCwdAlloc(allocator) |
std.process.currentPathAlloc(io, allocator) |
Application Guidance
- Prefer an application's virtual or asset file-system abstraction for runtime reads when one exists.
- New direct file operations still need an
ioparameter at the API boundary. - Writes should be explicit and use
std.Io.Dir/std.Io.Fileuntil a project abstraction owns write policy. - Avoid local fallback
Io.Threadedconstruction in low-level file helpers; pass or storeio.