Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Module Fields

Wax modules are defined by a sequence of top-level fields: types, functions, globals, and tags.

Types

Types are defined using type for single definitions or rec { ... } for mutually recursive types.

Simple Types

type point = { x: i32, y: i32 };
type bytes = [i8];
type callback = fn(_: i32) -> i32;

Recursive Types

Use rec when types reference each other:

rec {
    type node = { value: i32, next: &?list };
    type list = node;
}

Supertypes and Finality

Types are final (non-extensible) by default. Use open to allow subtypes:

type open shape = { x: i32, y: i32 };
type circle : shape = { radius: i32 };

Maps to:

(type $shape (sub (struct (field $x i32) (field $y i32))))
(type $circle (sub $shape (struct (field $x i32) (field $y i32) (field $radius i32))))

Functions

Definition

fn name(param: type, ...) -> return_type {
    body
}

Functions without a return type:

fn log_value(x: i32) {
    // no return
}

Function Signatures (Declarations)

Declare a function signature without a body (used with imports):

fn external_function(x: i32) -> i32;

Block Type Annotation

Functions can have a block type on the body:

fn example() -> i32 'label: {
    // body can use 'label
}

Globals

Immutable Globals

const PI: f64 = 3.14159;
const MAX_SIZE: i32 = 1024;

Mutable Globals

let mut counter: i32 = 0;
let mut state: &?any = null;

Imported Globals

#[import = ("env", "base")]
const base: i32;

#[import = ("env", "counter")]
let mut counter: i32;

Tags

Tags define exception types for structured exception handling.

Definition

tag my_error(code: i32);
tag empty_error();
tag multi_arg(a: i32, b: &string);

Maps to:

(tag $my_error (param i32))
(tag $empty_error)
(tag $multi_arg (param i32) (param (ref $string)))

Imported Tags

#[import = ("env", "js_error")]
tag js_error(&?extern);

Attributes

Attributes modify module fields. They use the syntax #[name = value] and appear before the field they modify.

Export Attribute

Export a field with a given name:

#[export = "add"]
fn add(x: i32, y: i32) -> i32 { x + y }

#[export = "PI"]
const PI: f64 = 3.14159;

#[export = "my_error"]
tag my_error(code: i32);

Multiple exports can share the same function:

#[export = "add"]
#[export = "plus"]
fn add(x: i32, y: i32) -> i32 { x + y }

Import Attribute

Import a field from a module. Takes a tuple of ("module", "name"):

#[import = ("env", "log")]
fn log(msg: i32);

#[import = ("env", "memory_base")]
const memory_base: i32;

#[import = ("env", "error")]
tag error(code: i32);

Combined Import and Export

A field can be both imported and re-exported:

#[import = ("env", "value")]
#[export = "value"]
const value: i32;

Tables and Memories

Wax focuses on GC-based memory management and does not provide dedicated syntax for linear memory or tables. When converting from WAT/WASM:

  • Memory definitions are dropped (Wax uses GC structs and arrays)
  • Table definitions are dropped (Wax uses typed function references)

If you need linear memory or tables, write that portion in WAT and link it with your Wax code.

Module Structure

A typical Wax module follows this structure:

// 1. Type definitions
type point = { x: i32, y: i32 };
type callback = fn(_: i32) -> i32;

// 2. Imported globals and functions
#[import = ("env", "log")]
fn log(value: i32);

#[import = ("env", "base")]
const base: i32;

// 3. Tags
tag my_error(code: i32);

// 4. Module globals
const FACTOR: i32 = 100;
let mut counter: i32 = 0;

// 5. Internal functions
fn helper(x: i32) -> i32 {
    x * FACTOR
}

// 6. Exported functions
#[export = "compute"]
fn compute(x: i32) -> i32 {
    counter = counter + 1;
    log(counter);
    helper(x + base)
}

This order is conventional but not required; Wax allows fields in any order.