Skip to content

Functions

Chung Leong edited this page Jul 21, 2024 · 5 revisions

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
  • accept a function pointer as an argument
  • return a data type that only exists in comptime
  • return a function pointer

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 given is only valid in for the duration of the call. Code that saves a copy of the allocator will likely crash when it tries to use it again. Memory from the allocator comes 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.

Variadic functions

Variable arguments passed to a C-style variadic function need to be explicitly typed:

const c = @cImport(
    @cInclude("stdio.h"),
);

pub const I32 = i32;
pub const I64 = i64;
pub const F64 = f64;
pub const CStr = [*:0]u8;

pub const printf = c.printf;
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.13.0, 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 pointer

Zigar currently does not support function pointers.


Error union

Clone this wiki locally