Interface struct ‣ File - chung-leong/zigar GitHub Wiki

The std.fs.File struct provides an interface to a file in Zig. Objects implementing the file interface can be passed to Zig functions accepting this struct as arguments.

A Zig function running in the main thread can only read from a synchronous source. Data must be available immediately, because the main thread runs the event loop and therefore cannot wait. A JavaScript string (for read purpose) or an array (for write purpose) will automatically get converted into a virtual file that satisfies this requirement:

A virtual file descriptor gets created during the conversion process. A close() function is attached to the original object, allowing you to release the descriptor:

const std = @import("std");

pub fn print(file: std.fs.File) !void {
    var write_buffer: [1024]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&write_buffer);
    const stdout = &stdout_writer.interface;
    var read_buffer: [1024]u8 = undefined;
    while (true) {
        const len = try file.read(&read_buffer);
        if (len == 0) break;
        _ = try stdout.write(read_buffer[0..len]);
    }
    try stdout.flush();
}
import { print } from './file-example-1.zig';

const s = Object('Hello world\n');
print(s);
s.close();
Hello world

Alternately, you can free the file on the Zig side:

const std = @import("std");

pub fn print(file: std.fs.File) !void {
    defer file.close();
    var write_buffer: [1024]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&write_buffer);
    const stdout = &stdout_writer.interface;
    var read_buffer: [1024]u8 = undefined;
    while (true) {
        const len = try file.read(&read_buffer);
        if (len == 0) break;
        _ = try stdout.write(read_buffer[0..len]);
    }
    try stdout.flush();
}

import { print } from './file-example-5.zig';

print('Hello world\n');
Hello world

Using TransformStream

Currently there's no support for using Zig readers and writers from JavaScript. What you can do is create a pass-thru TransformStream, pass its writable end to Zig as a virtual file, then and stream into it. The following example demonstrates how to decompress a HTTP stream from the fetch API with the help of std.compress.flate.Decompress:

const std = @import("std");

const zigar = @import("zigar");

var work_queue: zigar.thread.WorkQueue(worker) = .{};

pub const shutdown = work_queue.promisify(.shutdown);
pub const decompress = work_queue.promisify(worker.decompress);

const worker = struct {
    pub fn decompress(src: std.fs.File, dest: std.fs.File) !usize {
        var read_buffer: [16 * 1024]u8 = undefined;
        var reader = src.reader(&read_buffer);
        var decompress_buffer: [std.compress.flate.max_window_len]u8 = undefined;
        var decompressor: std.compress.flate.Decompress = .init(&reader.interface, .gzip, &decompress_buffer);
        var write_buffer: [16 * 1024]u8 = undefined;
        var writer = dest.writer(&write_buffer);
        const len = try decompressor.reader.streamRemaining(&writer.interface);
        try writer.interface.flush();
        return len;
    }
};
import { decompress, shutdown } from './file-example-4.zig';

const url = 'https://github.com/chung-leong/zigar/raw/refs/heads/main/zigar-compiler/test/integration/stream-handling/data/test.txt.gz';

try {
  const { body } = await fetch(url);
  const transform = new TransformStream(undefined, { highWaterMark: 1024 * 16 });
  decompress(body, transform.writable).then(() => transform.writable.close());
  const decoder = new TextDecoder();
  for await (const data of transform.readable) {
    console.log(decoder.decode(data));
  }
} finally {
  await shutdown();
}
Four score and seven years ago ...

Note:

Calling std.fs.File.stat() with a virtual file will crash on Linux on certain platforms due the use of direct syscalls.


Interface structs | File interface