MoonBit for Component Model#
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 toolchain#
Install the
wit-bindgenCLI tool, which generates MoonBit bindings from WIT files:$ cargo install wit-bindgen-cli
Install
wasm-toolsfor working with WebAssembly components:$ cargo install wasm-tools
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
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:adderwith version0.1.0Defines an
addinterface with a single function that takes twou32parameters and returns au32Creates an
adderworld that exports theaddinterface
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
│ └── top.mbt
├── gen
│ ├── ffi.mbt
│ ├── gen_interface_docs_adder_add_export.mbt
│ ├── interface
│ │ └── docs
│ │ └── adder
│ │ └── add
│ │ ├── moon.pkg
│ │ ├── stub.mbt
│ │ └── top.mbt
│ ├── moon.pkg
│ ├── world
│ │ └── adder
│ │ ├── moon.pkg
│ │ └── stub.mbt
│ └── world_adder_export.mbt
├── moon.mod.json
├── wit
│ └── world.wit
└── world
└── adder
├── ffi_import.mbt
├── import.mbt
├── moon.pkg
└── top.mbt
The generated files include:
moon.mod.json: MoonBit module configurationgen/: Generated export bindingsinterface/: Generated export interface bindingsworld/: Generated export world bindingsstub.mbt: Main implementation file
interface/: Generated import interface bindingsworld/: Generated import world bindings
4. Examine the Generated Code#
The wit-bindgen tool generates MoonBit bindings that handle the WebAssembly
component interface. Let's examine the generated
gen/interface/docs/adder/add/stub.mbt:
// Generated by `wit-bindgen` 0.45.0.
pub fn add(_x : UInt, _y : UInt) -> UInt {
...
}
The ... is the placeholder syntax in MoonBit. When executing
moon check --target wasm, 'unfinished code' warnings will appear.
5. Implement the Component Logic#
Now implement the add function in gen/interface/docs/adder/add/stub.mbt:
// Generated by `wit-bindgen` 0.45.0.
///|
pub fn add(x : UInt, y : UInt) -> UInt {
x + y
}
6. Configure the Build#
Ensure your gen/moon.pkg is properly configured for WebAssembly target:
import {
"docs/adder/ffi" @ffi,
"docs/adder/gen/interface/docs/adder/add" @add,
}
options(
link: {
"wasm": {
"exports": [
"cabi_realloc:cabi_realloc",
"wasmExportAdd:docs:adder/add@0.1.0#add",
],
"export-memory-name": "memory",
"heap-start-address": 16,
},
},
)
7. Build the WebAssembly Component#
Build the MoonBit code to WebAssembly:
$ moon build --target wasm
This generates a WebAssembly module. To create a proper WebAssembly component,
use wasm-tools:
$ wasm-tools component embed wit _build/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
Expected output for both commands:
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;
}
}
8. 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
9. Configurations#
--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:
// 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] {
...
}
which you may use it as:
// 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.