Functions ᴾᴴᴾ - chung-leong/zigar GitHub Wiki
JavaScript | PHP
A function written in Zig can be made available for use in PHP provided that it does not:
- accept any comptime argument
- accept
anytypeas an argument - return a data type that only exists in comptime
Naturally, the function would need to be made public using the pub keyword.
Memory allocator
A function that accepts std.mem.Allocator will automatically get one from Zigar. On the PHP side, it will end up with one fewer arguments:
const std = @import("std");
pub fn toUpperCase(allocator: std.mem.Allocator, s: []const u8) ![]const u8 {
return try std.ascii.allocUpperString(allocator, s);
}
<?php
$m = zigar_use(__DIR__ . '/function-example-1.zig');
$result = $m->toUpperCase('Hello world');
echo "$result\n";
HELLO WORLD
The allocator gets its memory from the PHP language engine, which employs garbage collection. There is never a need to manually free up memory.
Struct methods
A function defined within a struct that accepts an instance of the struct itself as the first argument can be invoked in PHP in the manner of an instance method:
pub const Rectangle = struct {
left: f64,
right: f64,
width: f64,
height: f64,
pub fn size(self: Rectangle) f64 {
return self.width * self.height;
}
};
<?php
$m = zigar_use(__DIR__ . '/function-example-2.zig');
$rect = new $m->Rectangle(left: 5, right: 10, width: 20, height: 10);
echo $rect->size(), "\n";
200
At the same time, it can be invoked like a static method:
<?php
$m = zigar_use(__DIR__ . '/function-example-2.zig');
echo $m->Rectangle->size(left: 5, right: 10, width: 30, height: 15), "\n";
450
Named arguments are passed as the last argument in an array (which is then converted to the struct type). The code above is equivalent to the following:
<?php
$m = zigar_use(__DIR__ . '/function-example-2.zig');
echo $m->Rectangle->size([ 'left' => 5, 'right' => 10, 'width' => 30, 'height' => 15]), "\n";
The self argument can be a pointer to the struct:
pub const Rectangle = struct {
left: f64,
right: f64,
width: f64,
height: f64,
pub fn size(self: *const Rectangle) f64 {
return self.width * self.height;
}
};
<?php
$m = zigar_use(__DIR__ . '/function-example-3.zig');
$rect = new $m->Rectangle(left: 5, right: 10, width: 20, height: 10);
echo $rect->size(), "\n";
echo $m->Rectangle->size(left: 5, right: 10, width: 30, height: 15), "\n";
200
450
Union, Enum, and Opaque can also have methods attached to them.
Optional argument
When the last argument to a function is a struct and all its fields have default values, that argument can be omitted. This feature can be used to provide default values to function options:
const std = @import("std");
const Options = struct {
version: i32 = 2,
uppercase: bool = true,
};
pub fn sha(allocator: std.mem.Allocator, bytes: []const u8, options: Options) ![]const u8 {
return inline for (.{
std.crypto.hash.Sha1,
std.crypto.hash.sha2.Sha256,
std.crypto.hash.sha3.Sha3_256,
}, 0..) |Algo, index| {
if (options.version == index + 1) {
var digest: [Algo.digest_length]u8 = undefined;
Algo.hash(bytes, &digest, .{});
const case: std.fmt.Case = if (options.uppercase) .upper else .lower;
const hex = std.fmt.bytesToHex(digest, case);
return try allocator.dupe(u8, &hex);
}
} else error.UnrecognizedVersion;
}
<?php
$m = zigar_use(__DIR__ . '/function-example-4.zig');
$text = "My hovercraft is full of eels";
echo $m->sha($text), "\n";
echo $m->sha($text, version: 2), "\n";
echo $m->sha($text, version: 1), "\n";
echo $m->sha($text, version: 3, uppercase: true), "\n";
5CBEF059C701BE06315EA9B38CCE126081A84E2A1CC5AC9C43DBB3C1E6A985C4
5CBEF059C701BE06315EA9B38CCE126081A84E2A1CC5AC9C43DBB3C1E6A985C4
10F0F962DCFBF9E5765181055AA11A5F4A3F6ECF
74474BF0B57D67A82BC41BD24912687EC2B60308AA3BA1110B8C053ADC5D361D
Returning errors
When a function returning an error union returns an error (instead of a value), an exception gets thrown:
const std = @import("std");
pub fn buy(_: []const u8) !void {
return error.ItIsScratched;
}
<?php
$m = zigar_use(__DIR__ . '/function-example-5.zig');
try {
$m->buy("record");
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
it is scratched
The exception object's message is formed by "decamelizing" the name of the error in Zig (which should be camel-case per convention).
Variadic functions
Variable arguments passed to a C-style variadic function need to be explicitly typed:
const c = @import("c");
pub const printf = c.printf;
pub const I32 = i32;
pub const I64 = i64;
pub const F64 = f64;
pub const CStr = [*:0]u8;
<?php
$m = zigar_use(__DIR__ . '/variadic-function-example-1.zig');
$m->printf("i32: %d\n", new $m->I32(1234));
$m->printf("i64: %lx\n", new $m->I64(0x7777_7777_7777_7777));
$m->printf("f64: %.5f\n", new $m->F64(M_PI));
$m->printf("string: %s\n", new $m->CStr('Hello world'));
i32: 1234
i64: 7777777777777777
f64: 3.14159
string: Hello world
printf has one fixed argument--the format string. This does not need explicit typing since Zigar knows what's expected. Arguments after the format string must be Zigar data object.
It's possible to write variadic functions in Zig:
const std = @import("std");
pub const I64 = i64;
pub fn print(count: usize, ...) callconv(.c) void {
var va_list = @cVaStart();
defer @cVaEnd(&va_list);
for (0..count) |_| {
const number = @cVaArg(&va_list, i64);
std.debug.print("{x}\n", .{number});
}
}
<?php
$m = zigar_use(__DIR__ . '/variadic-function-example-2.zig');
$m->print(4,
new $m->I64(0x7777_7777_7777_7777),
new $m->I64(0x7777_7777_7777_0000),
new $m->I64(0x7777_7777_0000_0000),
new $m->I64(0x7777_0000_0000_0000),
);
7777777777777777
7777777777770000
7777777700000000
7777000000000000
For now you should not use this feature. As of version 0.15.2, support for variadic functions
is still incomplete. It does not work at all when the compilation target is x86_64-windows
or aarch64-linux. printf and friends do work on these platforms.
Zigar uses archecture-specific code to handle variadic functions. Support is currently limited
to wasm32, x86, x86_64, arm, aarch64, riscv64, and powerpc64le.
The number of arguments you can pass to a variadic function is not unlimited. Up to 1024 bytes can be used on a 32-bit platform, while 2048 bytes can be used on a 64-bit latform. This translates to roughly 256 arguments.