6.4 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.
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);
Common options remain conceptually similar: truncate, exclusive create, read access, mode/permissions, and locking where supported.
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.
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", .{});
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");
Use openDir options for iteration/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.
relative, relativeWindows, and relativePosix are pure: pass the current directory and optional environment map instead of letting the function query the OS.
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
Use std.Io.File.Atomic or directory atomic helpers instead of hand-rolled random temporary names. The 0.16 implementation is routed through std.Io, including entropy.
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.