Functions - chung-leong/zigar GitHub Wiki

JavaScript | PHP


A function written in Zig can be made available for use in JavaScript provided that it does not:

  • accept any comptime argument
  • accept anytype as 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 JavaScript 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);
}
import { toUpperCase } from './function-example-1.zig';

console.log(toUpperCase('Hello world').string);
HELLO WORLD

The allocator gets its memory from the JavaScript 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 JavaScript 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;
    }
};
import { Rectangle } from './function-example-2.zig';

const rect = new Rectangle({ left: 5, right: 10, width: 20, height: 10 });
console.log(rect.size());
200

At the same time, it can be invoked like a static method:

import { Rectangle } from './function-example-2.zig';

console.log(Rectangle.size({ left: 5, right: 10, width: 30, height: 15 }));
450

It works the same way when the self argument is a pointer:

pub const Rectangle = struct {
    left: f64,
    right: f64,
    width: f64,
    height: f64,

    pub fn size(self: *const Rectangle) f64 {
        return self.width * self.height;
    }
};
import { Rectangle } from './function-example-3.zig';

const rect = new Rectangle({ left: 5, right: 10, width: 20, height: 10 });
console.log(rect.size());
console.log(Rectangle.size({ left: 5, right: 10, width: 30, height: 15 }));
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;
}
import { sha } from './function-example-4.zig';

const text = "My hovercraft is full of eels";

console.log(sha(text).string);
console.log(sha(text, { version: 2 }).string);
console.log(sha(text, { version: 1 }).string);
console.log(sha(text, { version: 3, uppercase: true }).string);
5CBEF059C701BE06315EA9B38CCE126081A84E2A1CC5AC9C43DBB3C1E6A985C4
5CBEF059C701BE06315EA9B38CCE126081A84E2A1CC5AC9C43DBB3C1E6A985C4
10F0F962DCFBF9E5765181055AA11A5F4A3F6ECF
74474BF0B57D67A82BC41BD24912687EC2B60308AA3BA1110B8C053ADC5D361D

Returning errors

When a function returning an error union returns an error (instead of a value), an error gets thrown:

const std = @import("std");

pub fn buy(_: []const u8) !void {
    return error.ItIsScratched;
}
import { buy } from './function-example-5.zig';

try {
    buy("record");
} catch (err) {
    console.log(err.message);
}
It is scratched

The error 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;
import { printf, I32, I64, F64, CStr } from './variadic-function-example-1.zig';

printf("i32: %d\n", new I32(1234));
printf("i64: %lx\n", new I64(0x7777_7777_7777_7777n));
printf("f64: %.5f\n", new F64(Math.PI));
printf("string: %s\n", new 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});
    }
}
import { print, I64 } from './variadic-function-example-2.zig';

print(4,
    new I64(0x7777_7777_7777_7777n),
    new I64(0x7777_7777_7777_0000n),
    new I64(0x7777_7777_0000_0000n),
    new I64(0x7777_0000_0000_0000n),
);
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. The limit does not apply to WebAssembly.


Function pointers | Async functions | Error union