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

JavaScript | PHP


The std.fs.File struct provides an interface to a file in Zig. When used as an argument to function exported to PHP, it accepts a PHP stream resource. During the conversion process a virtual file descriptor is created. This file descriptor is closed automatically when the resource is freed.

The following example prints the contents of a PHP memory stream:

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();
}
<?php

$m = zigar_use(__DIR__ . '/file-example-1.zig');

$strm = fopen("php://memory", "w+");
for ($i = 0; $i < 3; $i++) {
    fwrite($strm, "Hello world\n");
}
rewind($strm);
$m->print($strm);
fclose($strm);
Hello world
Hello world
Hello world

This happens even when the resource isn't freed explicitly:

const std = @import("std");

var copy: std.fs.File = undefined;

pub fn print(file: std.fs.File) !void {
    // save a copy for printAgain()
    copy = file;
    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();
}

pub fn printAgain() !void {
    try print(copy);
}
<?php

$m = zigar_use(__DIR__ . '/file-example-6.zig');

$strm = fopen("php://memory", "w+");
for ($i = 0; $i < 3; $i++) {
    fwrite($strm, "Hello world\n");
}
rewind($strm);
$m->print($strm);
rewind($strm);
$m->printAgain();
// this prints nothing since the file offset is at the end
$m->printAgain();
$strm = null;
// the file descriptor is not valid at this point
try {
    $m->printAgain();
} catch (Exception $e) {
    echo "$e\n";
}
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
ZigException: not open for reading in /home/rwiggum/examples/file-example-6.php:18
Stack trace:
#0 /home/rwiggum/examples/file-example-6.php(18): file-example-6->printAgain()
#1 {main}

The file descriptor can be used to close 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();
}

<?php

$m = zigar_use(__DIR__ . '/file-example-5.zig');

$strm = fopen("php://memory", "w+");
fwrite($strm, "Hello world\n");
rewind($strm);
$m->print($strm);
// this call will fail
fclose($strm);
Hello world
PHP Fatal error:  Uncaught TypeError: fclose(): supplied resource is not a valid stream resource in /home/rwiggum/examples/file-example-5.php:10

Note:

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


Interface structs