MoonBit

MoonBit is a programming language that provides first-class support for modern WebAssembly, including WebAssembly components.

This guide demonstrates how to build WebAssembly components using MoonBit, leveraging WIT (WebAssembly Interface Types) for interface definitions and the wit-bindgen toolchain for code generation.

This tutorial walks through building a component that implements the adder world defined in the docs:adder package. The component will export an add interface containing an add function that sums two numbers.

1. Install the Tools

Installing MoonBit

First, install the MoonBit compiler and toolchain. Follow the installation instructions from the MoonBit download page.

Verify your MoonBit installation (below are the versions at the time of writing):

$ moon version --all
moon 0.1.20250826 (8ab6c9e 2025-08-26) ~/.moon/bin/moon
moonc v0.6.25+d6913262c (2025-08-27) ~/.moon/bin/moonc
moonrun 0.1.20250826 (8ab6c9e 2025-08-26) ~/.moon/bin/moonrun
moon-pilot 0.0.1-95f12db ~/.moon/bin/moon-pilot

Installing Wasm toolchains

You need to have the following tools:

You may choose to download the executable from the respective GitHub releases, or you may, with Rust toolchain installed:

cargo install wit-bindgen-cli
cargo install wasm-tools
cargo install wasmtime-cli

Verify the installations (below are the versions at the time of writing):

$ wit-bindgen --version
wit-bindgen-cli 0.45.0
$ wasm-tools --version
wasm-tools 1.238.0
$ wasmtime --version
wasmtime 36.0.2

2. Define the Interface (WIT)

Before generating the MoonBit project, you need to define the component interface using WIT.

Create a directory for your project and define the WIT file:

mkdir moonbit-adder && cd moonbit-adder
mkdir wit

Create wit/world.wit with the following content:

package docs:adder@0.1.0;

interface add {
    add: func(x: u32, y: u32) -> u32;
}

world adder {
    export add;
}

This WIT definition:

  • Declares a package docs:adder with version 0.1.0
  • Defines an add interface with a single function that takes two u32 parameters and returns a u32
  • Creates an adder world that exports the add interface

3. Generate MoonBit Project Structure

Use wit-bindgen to generate the MoonBit project structure and bindings:

wit-bindgen moonbit wit/world.wit --out-dir . \
    --derive-eq \
    --derive-show \
    --derive-error

This command generates the following directory structure:

.
├── ffi
│   ├── moon.pkg.json
│   └── top.mbt
├── gen
│   ├── ffi.mbt
│   ├── gen_interface_docs_adder_add_export.mbt
│   ├── interface
│   │   └── docs
│   │       └── adder
│   │           └── add
│   │               ├── moon.pkg.json
│   │               ├── stub.mbt
│   │               └── top.mbt
│   ├── moon.pkg.json
│   ├── world
│   │   └── adder
│   │       ├── moon.pkg.json
│   │       └── stub.mbt
│   └── world_adder_export.mbt
├── moon.mod.json
├── wit
│   └── world.wit
└── world
    └── adder
        ├── ffi_import.mbt
        ├── import.mbt
        ├── moon.pkg.json
        └── top.mbt

The generated files include:

  • moon.mod.json: MoonBit module configuration
  • gen/: Generated export bindings
    • interface/: Generated export interface bindings
    • world/: Generated export world bindings
    • stub.mbt: Main implementation file
  • interface/: Generated import interface bindings
  • world/: Generated import world bindings

The wit-bindgen tool generates MoonBit bindings that handle the WebAssembly component interface.

If you execute moon check, there will be a warning suggesting that file gen/interface/docs/adder/add/stub.mbt contains unfinished code, which is what we need to complete.

4. Implement the Component Logic

Implement the add function in gen/interface/docs/adder/add/stub.mbt:

#![allow(unused)]
fn main() {
// Generated by `wit-bindgen` 0.45.0.

///|
pub fn add(x : UInt, y : UInt) -> UInt {
  x + y
}
}

5. Configure the Build

Ensure your gen/moon.pkg.json is properly configured for WebAssembly target:

{
    // link configuration for Wasm backend
    "link": {
        "wasm": {
            "exports": [
                // Export for cabi_realloc
                "cabi_realloc:cabi_realloc",
                // Export per the interface definition
                "wasmExportAdd:docs:adder/add@0.1.0#add"
            ],
            "export-memory-name": "memory",
            "heap-start-address": 16
        }
    },
    "import": [
        {
            "path": "docs/adder/ffi",
            "alias": "ffi"
        },
        {
            "path": "docs/adder/gen/interface/docs/adder/add",
            "alias": "add"
        }
    ]
}

5. Build the WebAssembly Component

Build the MoonBit code to WebAssembly core module:

moon build

To create a proper WebAssembly component from the module we have produced, use wasm-tools:

wasm-tools component embed wit target/wasm/release/build/gen/gen.wasm \
    --encoding utf16 \
    --output adder.wasm
wasm-tools component new adder.wasm --output adder.component.wasm

You can verify the component's interface using wasm-tools:

wasm-tools component wit adder.component.wasm

The WIT printed should be similar if not exactly the same as the following:

package root:component;

world root {
  export docs:adder/add@0.1.0;
}
package docs:adder@0.1.0 {
  interface add {
    add: func(x: u32, y: u32) -> u32;
  }
}

6. Testing the Component

Using the Example Host

To test your component, use the example-host provided in this repository:

git clone https://github.com/bytecodealliance/component-docs.git
cd component-docs/component-model/examples/example-host
cp /path/to/adder.component.wasm .
cargo run --release -- 5 3 adder.component.wasm

Expected output:

5 + 3 = 8

Using Wasmtime

You can also test the component directly with wasmtime:

$ wasmtime run --invoke 'add(10, 20)' adder.component.wasm
30

Advanced

--derive-eq --derive-show

These two options will add derive(Eq) and / or derive(Show) for all the generated types.

--derive-error

This option will generate variants / enums whose names containing 'Error' as suberrors. This allows you to integrate the MoonBit's error handling easier.

For example, for the following interface:

package docs:adder@0.1.0;

interface add {
    variant computation-error {
        overflow
    }
    add: func(x: u32, y: u32) -> result<u32, computation-error>;
}

world adder {
    import add;
}

Will generate the following type and function

#![allow(unused)]
fn main() {
// Generated by `wit-bindgen` 0.45.0. DO NOT EDIT!

///|
pub(all) suberror ComputationError {
  Overflow
} derive(Show, Eq)

///|
pub fn add(x : UInt, y : UInt) -> Result[UInt, ComputationError] {
  let return_area = @ffi.malloc(8)
  wasmImportAdd(x.reinterpret_as_int(), y.reinterpret_as_int(), return_area)
  let lifted4 = match @ffi.load8_u(return_area + 0) {
    0 => Result::Ok(@ffi.load32(return_area + 4).reinterpret_as_uint())
    1 => {
      let lifted = match @ffi.load8_u(return_area + 4) {
        0 => ComputationError::Overflow
        _ => panic()
      }
      Result::Err(lifted)
    }
    _ => panic()
  }
  @ffi.free(return_area)
  return lifted4
}
}

which you may use it as:

#![allow(unused)]
fn main() {
// Generated by `wit-bindgen` 0.45.0.

///|
fn init {
  let _ = @add.add(1, 2).unwrap_or_error() catch { Overflow => ... }

}
}

--ignore-stub

It happens when you would like to regenerate the project due to the updated interface, but you don't want the stub file to be touched. You may use --ignore-stub option to avoid such modifications.

--project-name

By default, the project name is generated per the name defined in the MoonBit file. You may use this option to specify the name of the project. It can also be used if you are generating the project as part of a larger project.

--gen-dir

By default, the exportation parts are generated under gen. You may use this option to specify another directory.

References and Further Reading