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:
- wit-bindgen for generating MoonBit bindings from WIT
- wasm-tools for component conversion
- (optional) wasmtime for Wasm runtime
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 version0.1.0
- Defines an
add
interface with a single function that takes twou32
parameters and returns au32
- Creates an
adder
world that exports theadd
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 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
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.