Skip to content

Filesystem APIs

The standard library gives access to files and directories through std.fs.

The standard library gives access to files and directories through std.fs.

A small program can open a file and read its contents.

const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("notes.txt", .{});
    defer file.close();

    var buffer: [1024]u8 = undefined;

    const n = try file.readAll(buffer[0..]);

    std.debug.print("{s}", .{buffer[0..n]});
}

std.fs.cwd() returns a handle to the current working directory.

std.fs.cwd()

A directory handle is used to open files relative to that directory.

openFile("notes.txt", .{})

The second argument is an options struct. Here it is empty, so the defaults are used.

Opening a file can fail.

The file may not exist.

The process may not have permission.

The path may name a directory instead of a file.

For this reason, openFile returns an error union, and the call uses try.

const file = try std.fs.cwd().openFile("notes.txt", .{});

The file must be closed when the program is finished with it.

defer file.close();

The defer statement makes the close happen when main returns.

Reading into a fixed buffer is explicit.

var buffer: [1024]u8 = undefined;

The buffer is uninitialized. The file read fills part of it.

const n = try file.readAll(buffer[0..]);

readAll reads bytes into the slice and returns the number of bytes read.

Only the first n bytes contain file data.

buffer[0..n]

Writing a file is similar.

const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().createFile("out.txt", .{});
    defer file.close();

    try file.writeAll("hello\n");
}

createFile creates or truncates a file.

writeAll writes the whole slice or returns an error.

For small files, the standard library can allocate a buffer and read the whole file.

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const data = try std.fs.cwd().readFileAlloc(
        allocator,
        "notes.txt",
        1024 * 1024,
    );
    defer allocator.free(data);

    std.debug.print("{s}", .{data});
}

The last argument is the maximum number of bytes to read.

1024 * 1024

This limit matters. A program should not accidentally allocate memory for an enormous file.

The returned slice is owned by the caller.

defer allocator.free(data);

Directory operations also begin from a directory handle.

const std = @import("std");

pub fn main() !void {
    try std.fs.cwd().makeDir("tmp");
}

This creates a directory named tmp in the current working directory.

To delete an empty directory:

try std.fs.cwd().deleteDir("tmp");

To delete a file:

try std.fs.cwd().deleteFile("out.txt");

Paths are passed as byte slices.

[]const u8

The interpretation of paths depends on the operating system. Zig exposes filesystem operations without pretending that every platform is identical.

Iterating over a directory uses an opened iterable directory.

const std = @import("std");

pub fn main() !void {
    var dir = try std.fs.cwd().openDir(".", .{ .iterate = true });
    defer dir.close();

    var it = dir.iterate();

    while (try it.next()) |entry| {
        std.debug.print("{s}\n", .{entry.name});
    }
}

The entry contains a name and a kind.

entry.name
entry.kind

The name is relative to the directory being iterated.

The kind tells whether the entry is a file, directory, symbolic link, or something else.

A common program pattern is:

const dir = std.fs.cwd();

Then use dir for all file operations.

This keeps paths relative and makes code easier to test.

For programs that need absolute paths, use the appropriate functions in std.fs and std.process.

Filesystem code usually has many possible errors. Zig makes these errors part of the type of the operation.

A file operation can fail because the file is missing, a directory is missing, permissions are wrong, storage is full, or the operating system rejects the path.

The code does not need exceptions to express this.

It uses error unions.

try file.writeAll(data);

This is direct and visible.

Exercise 14-21. Create a file named hello.txt and write one line to it.

Exercise 14-22. Read hello.txt into a fixed buffer and print it.

Exercise 14-23. Read a file with readFileAlloc and free the returned memory.

Exercise 14-24. Iterate over the current directory and print each entry name.