Filesystem
Volt provides synchronous filesystem operations that work without a runtime, plus async variants for use inside the runtime. All APIs are under volt.fs.
Reading and Writing Files
Section titled “Reading and Writing Files”One-shot convenience functions
Section titled “One-shot convenience functions”const volt = @import("volt");
// Read an entire file into an allocated bufferconst data = try volt.fs.readFile(allocator, "config.json");defer allocator.free(data);
// Read as validated UTF-8 stringconst text = try volt.fs.readFileString(allocator, "README.md");defer allocator.free(text);
// Write a file (creates or truncates)try volt.fs.writeFile("output.txt", "Hello, Volt!");
// Append to a file (creates if needed)try volt.fs.appendFile("log.txt", "new entry\n");File handle
Section titled “File handle”For more control, open a File handle:
// Open for readingvar file = try volt.fs.File.open("data.bin");defer file.close();
var buf: [4096]u8 = undefined;const n = try file.read(&buf);// buf[0..n] contains the data
// Positioned read (doesn't change file position)const m = try file.pread(&buf, 1024); // read from offset 1024
// Create for writingvar out = try volt.fs.File.create("output.bin");defer out.close();
try out.writeAll("binary data here");try out.syncAll(); // flush to diskOpenOptions builder
Section titled “OpenOptions builder”var file = try volt.fs.File.getOptions() .setRead(true) .setWrite(true) .setCreate(true) .setTruncate(false) .open("state.db");defer file.close();Seeking
Section titled “Seeking”var file = try volt.fs.File.open("data.bin");defer file.close();
try file.seekTo(100); // absolute positiontry file.seekBy(-10); // relative offsettry file.rewind(); // back to startconst pos = try file.getPos(); // current positionDirectory Operations
Section titled “Directory Operations”Reading directories
Section titled “Reading directories”var dir = try volt.fs.readDir("/tmp");defer dir.close();
while (try dir.next()) |entry| { if (entry.isFile()) { // process file } else if (entry.isDir()) { // process subdirectory }}Creating and removing directories
Section titled “Creating and removing directories”// Create a single directorytry volt.fs.createDir("output");
// Create nested directories (like mkdir -p)try volt.fs.createDirAll("output/reports/2024");
// Remove empty directorytry volt.fs.removeDir("output/old");
// Remove directory and all contents recursivelytry volt.fs.removeDirAll(allocator, "output/temp");
// Remove a filetry volt.fs.removeFile("output/stale.txt");File Metadata
Section titled “File Metadata”const meta = try volt.fs.metadata("config.json");
const size = meta.size();const file_type = meta.fileType();const modified = meta.modified();const perms = meta.permissions();
// Check existenceif (volt.fs.exists("config.json")) { // file exists}File Copy and Move
Section titled “File Copy and Move”// Copy a file (returns bytes copied)const bytes = try volt.fs.copy("source.txt", "dest.txt");
// Rename/move a file or directorytry volt.fs.rename("old_name.txt", "new_name.txt");
// Symbolic linkstry volt.fs.symlink("target.txt", "link.txt");const target = try volt.fs.readLink(allocator, "link.txt");defer allocator.free(target);
// Hard linkstry volt.fs.hardLink("original.txt", "hardlink.txt");Memory-Mapped Files
Section titled “Memory-Mapped Files”Memory mapping lets the OS page file data in and out on demand. Only the pages being actively accessed are in physical memory — no buffer allocation or copy needed. Multiple threads can read the same mapping concurrently.
const volt = @import("volt");
// Map a file read-only -- zero copy, kernel handles pagingvar mapped = try volt.fs.mmapFile("data.csv", .{});defer mapped.unmap();
// mapped.slice() is []const u8 -- pass directly to any parserconst data = mapped.slice();Read-write mapping
Section titled “Read-write mapping”Changes to a read-write mapping are backed by the file on disk via MAP_SHARED:
var mapped = try volt.fs.mmapFile("output.dat", .{ .protection = .read_write });defer mapped.unmap();
const buf = try mapped.sliceMut();@memcpy(buf[offset..][0..chunk.len], chunk);try mapped.sync(); // flush to diskAccess pattern hints
Section titled “Access pattern hints”Tell the kernel how you plan to access the data:
// Sequential scan -- kernel increases read-aheadvar mapped = try volt.fs.mmapFile("data.csv", .{ .sequential = true });defer mapped.unmap();
// Random access -- kernel disables read-aheadvar mapped2 = try volt.fs.mmapFile("index.db", .{ .random = true });defer mapped2.unmap();
// Eager population -- pre-fault all pages (avoids page faults during access)var mapped3 = try volt.fs.mmapFile("hot_data.bin", .{ .populate = true });defer mapped3.unmap();Mapping from an open file handle
Section titled “Mapping from an open file handle”If you already have an open File, map it without re-opening:
var file = try volt.fs.File.open("data.parquet");defer file.close();
var mapped = try volt.fs.mmapHandle(&file, .{});defer mapped.unmap();
// Both file handle and mapping are usableconst header = mapped.slice()[0..4];Advising mapped regions
Section titled “Advising mapped regions”After mapping, give the kernel fine-grained hints for specific byte ranges:
var mapped = try volt.fs.mmapFile("data.parquet", .{});defer mapped.unmap();
// Prefetch the row group we're about to decodetry mapped.advise(rg_offset, rg_size, .will_need);
// Release pages we're done withtry mapped.advise(prev_rg_offset, prev_rg_size, .dont_need);Advisory File Hints
Section titled “Advisory File Hints”For regular (non-mapped) files, advise the kernel about your access pattern. On Linux this calls posix_fadvise; on macOS it uses fcntl(F_RDAHEAD).
var file = try volt.fs.File.open("data.parquet");defer file.close();
// Sequential scan -- kernel doubles read-ahead windowtry file.advise(.sequential);
// Or advise a specific byte rangetry file.adviseRange(rg_offset, rg_size, .will_need);try file.adviseRange(skipped_offset, skipped_size, .dont_need);File Size Without Opening
Section titled “File Size Without Opening”Get a file’s size in one call without opening a handle:
const size = try volt.fs.fileSize("data.csv");const buf = try allocator.alloc(u8, size);Reading Into a Pre-Allocated Buffer
Section titled “Reading Into a Pre-Allocated Buffer”readFull fills a buffer as much as possible without erroring on EOF:
var file = try volt.fs.File.open("data.bin");defer file.close();
var buf: [256 * 1024]u8 = undefined;const n = try file.readFull(&buf);// n <= buf.len, no error if file is shorter than bufferUnlike readAll (which returns error.EndOfStream if the buffer can’t be filled), readFull simply returns however many bytes were available.
Async File I/O
Section titled “Async File I/O”Inside the runtime, use AsyncFile for non-blocking operations (uses io_uring on Linux, blocking pool elsewhere):
fn processFiles(io: volt.Io) !void { // Async read const data = try volt.fs.readFileAsync(io.runtime, allocator, "large_file.bin"); defer allocator.free(data);
// Async write try volt.fs.writeFileAsync(io.runtime, "output.bin", data);}Or wrap synchronous operations with io.concurrent() to avoid blocking worker threads:
fn readConfig(io: volt.Io) ![]const u8 { var f = try io.concurrent(struct { fn run() ![]const u8 { return volt.fs.readFile(std.heap.page_allocator, "config.json"); } }.run, .{}); return try f.@"await"(io);}API Summary
Section titled “API Summary”| Function | Runtime needed? | Notes |
|---|---|---|
readFile(allocator, path) | No | Read entire file |
writeFile(path, data) | No | Create/truncate and write |
appendFile(path, data) | No | Append to file |
fileSize(path) | No | Get file size in bytes |
File.open(path) | No | Open file handle |
File.readFull(buf) | No | Fill buffer, no error on EOF |
File.advise(hint) | No | Advise kernel about access pattern |
File.adviseRange(off, len, hint) | No | Advise for a specific byte range |
mmapFile(path, opts) | No | Memory-map a file |
mmapHandle(file, opts) | No | Memory-map an open file handle |
readDir(path) | No | Iterate directory entries |
createDirAll(path) | No | Create nested directories |
copy(src, dst) | No | Copy file |
rename(old, new) | No | Move/rename |
metadata(path) | No | Get file metadata |
exists(path) | No | Check existence |
readFileAsync(rt, alloc, path) | Yes | Async read (io_uring or pool) |
writeFileAsync(rt, path, data) | Yes | Async write |
AsyncFile.open(rt, path) | Yes | Async file handle |