Go Tooling
The TinyGo compiler v0.34.0 and above has native support for the WebAssembly Component Model and WASI 0.2.0.
This guide walks through building a component that implements
the adder
world defined in the adder/world.wit
package.
The component will implement the adder
world,
which contains an add
interface with an add
function.
1. Install the tools
Follow the TinyGo installation instructions to install the TinyGo compiler.
Additionally, install the wasm-tools
CLI tool from the wasm-tools repository.
warning
wit-bindgen-go
comes with its own wasm-tools
vendored version, but tinygo still requires you to install it.
Even if unlikely, this could lead to version mismatch when using older versions of wasm-tools
.
Please make sure to keep your local wasm-tools
udpated, should you encounter any issues.
If using the Rust toolchain to install wasm-tools
, it can be installed like so:
cargo install --locked wasm-tools@1.235.0 --force
or via cargo binstall:
cargo binstall wasm-tools@1.235.0
To verify the installation, run the following commands:
$ tinygo version
tinygo version 0.34.0 ...
$ wasm-tools -V
wasm-tools 1.255.0 ...
Optional: Install the wkg
CLI tool to resolve the imports in the WIT file.
The wkg
CLI is a part of the Wasm Component package manager.
See the wasm-pkg-tools installation instructions to install manually or using cargo
.
2. Create your Go project
Now, create your Go project:
mkdir add && cd add
go mod init example.com
Install the following tool
:
go get -tool go.bytecodealliance.org/cmd/wit-bindgen-go
note
go tool
was introduced in Golang 1.24 and can be used to manage tooling in Go projects.
Consider also running go mod tidy
after adding the above tool.
2. Determine which world the component will implement
Since we will be implementing the adder
world, we can copy the WIT to our project.
Create a subdirectory called wit
and paste the following code
into a file called wit/component.wit
:
package docs:adder@0.1.0;
interface add {
add: func(x: u32, y: u32) -> u32;
}
world adder {
include wasi:cli/imports@0.2.0;
export add;
}
The line include wasi:cli/imports@0.2.0
is necessary because
we are using the wasip2
target of TinyGo.
TinyGo assumes that the component targets the wasi:cli/command@0.2.0
world
(part of wasi:cli
),
so it requires the imports of wasi:cli/imports@0.2.0
.
Using wkg
to automatically resolve and download imports
Tools like wkg
can be convenient to build a complete WIT package by resolving the imports.
Running the wkg wit build
command encodes the WIT into the Component Model binary format.
As a side effect, it resolves the imports
and populates your wit
folder with all relevant imported namespaces and packages.
$ wkg wit build
WIT package written to docs:adder@0.1.0.wasm
3. Generate bindings for the Wasm component
Now that we have our WIT definitions bundled together into a .wasm
file,
we can generate the bindings for our WebAssembly component, by adding a build directive:
go tool wit-bindgen-go generate --world adder --out internal ./docs:adder@0.1.0.wasm
note
The go tool
directive (added in Golang 1.24) installs and enables use of wit-bindgen-go
,
part of the Bytecode Alliance suite of Golang tooling.
The internal
directory will contain the generated Go code for that WIT package.
$ tree internal
internal
├── docs
│ └── adder
│ ├── add
│ │ ├── add.exports.go
│ │ ├── add.wasm.go
│ │ ├── add.wit.go
│ │ └── empty.s
│ └── adder
│ └── adder.wit.go
└── wasi
├── cli
│ ├── environment
│ │ ├── empty.s
│ │ ├── environment.wasm.go
│ │ └── environment.wit.go
│ ├── exit
│ │ ├── empty.s
│ │ ├── exit.wasm.go
│ │ └── exit.wit.go
│ ├── stderr
│ │ ├── empty.s
│ │ ├── stderr.wasm.go
│ │ └── stderr.wit.go
│ ├── stdin
│ │ ├── empty.s
│ │ ├── stdin.wasm.go
│ │ └── stdin.wit.go
│ ├── stdout
│ │ ├── empty.s
│ │ ├── stdout.wasm.go
│ │ └── stdout.wit.go
│ ├── terminal-input
│ │ ├── empty.s
│ │ ├── terminal-input.wasm.go
│ │ └── terminal-input.wit.go
│ ├── terminal-output
│ │ ├── empty.s
│ │ ├── terminal-output.wasm.go
│ │ └── terminal-output.wit.go
│ ├── terminal-stderr
│ │ ├── empty.s
│ │ ├── terminal-stderr.wasm.go
│ │ └── terminal-stderr.wit.go
│ ├── terminal-stdin
│ │ ├── empty.s
│ │ ├── terminal-stdin.wasm.go
│ │ └── terminal-stdin.wit.go
│ └── terminal-stdout
│ ├── empty.s
│ ├── terminal-stdout.wasm.go
│ └── terminal-stdout.wit.go
├── clocks
│ ├── monotonic-clock
│ │ ├── empty.s
│ │ ├── monotonic-clock.wasm.go
│ │ └── monotonic-clock.wit.go
│ └── wall-clock
│ ├── empty.s
│ ├── wall-clock.wasm.go
│ └── wall-clock.wit.go
├── filesystem
│ ├── preopens
│ │ ├── empty.s
│ │ ├── preopens.wasm.go
│ │ └── preopens.wit.go
│ └── types
│ ├── abi.go
│ ├── empty.s
│ ├── types.wasm.go
│ └── types.wit.go
├── io
│ ├── error
│ │ ├── empty.s
│ │ ├── error.wasm.go
│ │ └── error.wit.go
│ ├── poll
│ │ ├── empty.s
│ │ ├── poll.wasm.go
│ │ └── poll.wit.go
│ └── streams
│ ├── empty.s
│ ├── streams.wasm.go
│ └── streams.wit.go
├── random
│ ├── insecure
│ │ ├── empty.s
│ │ ├── insecure.wasm.go
│ │ └── insecure.wit.go
│ ├── insecure-seed
│ │ ├── empty.s
│ │ ├── insecure-seed.wasm.go
│ │ └── insecure-seed.wit.go
│ └── random
│ ├── empty.s
│ ├── random.wasm.go
│ └── random.wit.go
└── sockets
├── instance-network
│ ├── empty.s
│ ├── instance-network.wasm.go
│ └── instance-network.wit.go
├── ip-name-lookup
│ ├── abi.go
│ ├── empty.s
│ ├── ip-name-lookup.wasm.go
│ └── ip-name-lookup.wit.go
├── network
│ ├── abi.go
│ ├── empty.s
│ ├── network.wasm.go
│ └── network.wit.go
├── tcp
│ ├── abi.go
│ ├── empty.s
│ ├── tcp.wasm.go
│ └── tcp.wit.go
├── tcp-create-socket
│ ├── empty.s
│ ├── tcp-create-socket.wasm.go
│ └── tcp-create-socket.wit.go
├── udp
│ ├── abi.go
│ ├── empty.s
│ ├── udp.wasm.go
│ └── udp.wit.go
└── udp-create-socket
├── empty.s
├── udp-create-socket.wasm.go
└── udp-create-socket.wit.go
39 directories, 91 files
The add.exports.go
file contains an Exports
struct, containing declarations for
the exported functions that need to be implemented in the Go code.
4. Implement the add
Function
In your add
directory, create a file called main.go
and paste the following code into it:
//go:generate go tool wit-bindgen-go generate --world adder --out internal ./docs:adder@0.1.0.wasm
package main
import (
"example.com/internal/docs/adder/add"
)
func init() {
add.Exports.Add = func(x uint32, y uint32) uint32 {
return x + y
}
}
// main is required for the `wasi` target, even if it isn't used.
func main() {}
Go's init
function is used to do initialization tasks
that should be done before any other tasks.
In this case, we are using it to export the Add
function.
5. Build the Component
We can build our component using TinyGo.
Under the hood, TinyGo invokes wasm-tools
to embed the WIT file to the module and componentize it.
tinygo build -target=wasip2 \
-o adder.wasm \
--wit-package docs:adder@0.1.0.wasm \
--wit-world adder main.go
- The
-target=wasip2
flag specifies that the code should be compiled to WebAssembly using Preview 2 methods. - The
-o adder.wasm
flag directs the output to be saved inadd.wasm
in the current directory. - The
--wit-package
flag specifies the package name for the WIT code we are using. - The
--wit-world
flag specifies that the WIT world that defines the imports and exports for our component isadder
.
We now have an adder
component that satisfies our adder
world, exporting the add
function.
warning
By default, tinygo includes all debug-related information in your .wasm file.
That is desirable when prototyping or testing locally to obtain useful backtraces in case of errors
(for example, with wasmtime::WasmBacktraceDetails::Enable
).
To remove debug data and optimize your binary file, build with -no-debug
.
The resulting .wasm file will be considerably smaller (up to 75% reduction in size).
We can confirm using the wasm-tools component wit
command:
$ wasm-tools component wit adder.wasm
package root:component;
world root {
import wasi:io/error@0.2.0;
import wasi:io/streams@0.2.0;
import wasi:cli/stdout@0.2.0;
import wasi:random/random@0.2.0;
export add: func(x: s32, y: s32) -> s32;
}
...
5. Testing the add
Component
The following section requires you to have a Rust toolchain installed.
To run our add component, we need to use a host program with a WASI runtime that understands
the example
world.
This repository contains an example WebAssembly host written in Rust
that can run components that implement the adder
world.
git clone https://github.com/bytecodealliance/component-docs.git
cd component-docs/component-model/examples/example-host
cargo run --release -- 1 2 <PATH>/adder.wasm
- The double dashes separate the flags passed to
cargo
from the flags passed in to your code. - The arguments 1 and 2 are the arguments to the adder.
- In place of
<PATH>
, substitute the directory that contains your generatedadder.wasm
file.
Note: When hosts run components that use WASI interfaces, they must explicitly add WASI to the linker to run the built component.
A successful run should show the following output (of course, the paths to your example host and adder component will vary):
cargo run --release -- 1 2 adder.wasm
Compiling example-host v0.1.0 (/path/to/component-docs/component-model/examples/example-host)
Finished `release` profile [optimized] target(s) in 7.85s
Running `target/debug/example-host 1 2 /path/to/adder.wasm`
1 + 2 = 3
If not configured correctly, you may see errors like the following:
cargo run --release -- 1 2 adder.wasm
Compiling example-host v0.1.0 (/path/to/component-docs/component-model/examples/example-host)
Finished `release` profile [optimized] target(s) in 7.85s
Running `target/release/example-host 1 2 /path/to/adder.component.wasm`
Error: Failed to instantiate the example world
Caused by:
0: component imports instance `wasi:io/error@0.2.2`, but a matching implementation was not found in the linker
1: instance export `error` has the wrong type
2: resource implementation is missing
This kind of error normally indicates that the host in question does not satisfy WASI imports.