BattlefyBlogHistoryOpen menu
Close menuHistory

Zig and WebAssembly are a match made in heaven

Ronald Chen October 24th 2022

Zig is a modern system programming language that succeeds C. Three Zig features make it perfect for WebAssembly.

Note this post assumes Zig 0.10.0 or newer. The WebAssembly integration is still in development at the time of writing.

libc not required

libc is the C standard library that allows access to things like threads and standard IO streams.

WebAssembly's security model intentionally does not provide access to these low-level side effects. WebAssembly can only use libc via WASI.

This implies that any programming language that depends on libc also requires WASI, which is less secure than not using WASI at all. Zig can compile to both freestanding (I.E., no WASI) and WASI directly.

Some other so-called "WebAssembly first" languages are really "WASI only" languages.

Manual memory management

WebAssembly only offers linear memory with 64KiB pages and no garbage collector.

All memory management in Zig is manual and explicit. Even Zig's standard library takes in an allocator parameter to allow us to control precisely how memory is allocated.

Zig's std.heap.page_allocator compiles down to calls to WebAssembly memory.grow instruction.

This allows us to use Zig's standard library when targeting WebAssembly.

extern and export functions

WebAssembly allows us to import and export functions. This allows WebAssembly to call JavaScript and JavaScript to call WebAssembly.

Zig was designed with simple and direct interop with C and thus can define extern and export functions. This feature is also used to import/export functions with WebAssembly.

Let's look at a simple example. In adder.zig, we define two functions. b is imported and add is exported.

extern "app" fn b() i32;

export fn add(a: i32) i32 {
  return a + b();
}

Aside: Zig's CLI makes it easy to compile this file to WebAssembly. zig build-lib adder.zig -target wasm32-freestanding -dynamic -femit-bin=adder.wasm

In JavaScript, we import and create an instance of the WebAssembly module.

import wasmUrl from "./adder.wasm?url"

const module = await WebAssembly.compileStreaming(fetch(wasmUrl))
const { exports: { add } } = await WebAssembly.instantiate(module, {
  app: {
    b() {
      return 2
    }
})

console.log(add(1)) // prints 3

Note the ?url bit is Vite's way of getting an URL to a file instead of the file itself.

Be wary of papercuts

While Zig is by far the best language I've ever used to write WebAssembly, it is still a work in progress.

Some notable issues:

Do you want to learn the latest web tech? You're in luck, Battlefy is hiring.

2022

Powered by
BATTLEFY