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

Host interoperability

After postprocessing, host functions exchange actual Wasm references. JavaScript receives GC objects and host values directly, without numeric handles.

Loading a module

In a Rust library project, put the following examples in src/lib.rs and configure Cargo.toml:

[lib]
crate-type = ["cdylib"]

Build the library for wasm32-unknown-unknown and postprocess it:

rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release
wasmgc target/wasm32-unknown-unknown/release/your_crate.wasm -o app.wasm

Replace your_crate.wasm with the library’s output filename. Serve app.wasm alongside your page and load it with:

const bytes = await (await fetch("./app.wasm")).arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);
const wasm = instance.exports;

Struct exports

Choose which operations to expose to the host:

use wasmgc::{Gc, wasmgc};

#[wasmgc(constructor)]
pub struct Counter {
    #[getter]
    #[setter]
    value: i32,
}

JavaScript can construct and update a counter:

const counter = wasm["Counter.new"](1);
wasm["Counter.set_value"](counter, 2);
console.log(wasm["Counter.get_value"](counter)); // 2

constructor exports Counter.new, with parameters in field order. Field #[getter] and #[setter] attributes independently export the corresponding operations. These options only control host exports: Rust always gets its field methods, even when the attributes are omitted.

Function exports

Apply #[wasmgc] to a free function to export it under its Rust name:

#[wasmgc]
pub fn counter(value: i32) -> Gc<Counter> {
    Gc::new(Counter { value })
}

#[wasmgc]
pub fn read(counter: &Gc<Counter>) -> i32 {
    counter.value()
}
const value = wasm.counter(3);
console.log(wasm.read(value)); // 3

The functions remain callable from Rust. Parameters may be owned values or shared borrows; return values must be owned or (). Supported values include scalars, Gc<T>, GcArray<T>, Option<Gc<T>>, and ExternRef. Functions must be synchronous and non-generic. Export names do not include Rust module paths.

Host imports and ExternRef

ExternRef keeps a host value alive. Declare host operations in a #[wasmgc] extern "wasmgc" block; imports use the env module and their Rust function names.

use wasmgc::ExternRef;

#[wasmgc]
extern "wasmgc" {
    /// # Safety
    ///
    /// The host value must have an add(i32) method returning an i32 normally.
    fn counter_add(counter: &ExternRef, delta: i32) -> i32;
}

/// # Safety
///
/// `counter` must satisfy the `counter_add` host contract.
#[wasmgc]
pub unsafe fn increment(counter: ExternRef) -> i32 {
    // SAFETY: The host supplies a counter satisfying counter_add's contract.
    unsafe { counter_add(&counter, 1) }
}

Supply the import when instantiating the processed module:

const { instance } = await WebAssembly.instantiate(bytes, {
    env: { counter_add: (counter, delta) => counter.add(delta) },
});
const counter = { value: 0, add(delta) { return this.value += delta; } };
console.log(instance.exports.increment(counter)); // 1

Imports are unsafe to call by default. Host implementations must satisfy their declared contract and return normally; unwinding through the generated bridges is unsupported. An import may use safe fn when its host implementation is safe for every argument allowed by its Rust types.

ExternRef can also be stored in GC fields and arrays. Cloning or storing a reference retains the same host value; it does not copy the host object.