Appearance
Run WebAssembly from Go
Start here when Wago should live inside your Go process. You will build the same guest in WAT, AssemblyScript, or TinyGo, then compile it, create an instance, and call its add export from Go.
Create a small project
You need Go 1.22 or newer and one guest compiler: WABT, AssemblyScript, or TinyGo.
sh
mkdir wago-embed
cd wago-embed
go mod init example.com/wago-embed
go get github.com/wago-org/wago@main
mkdir guestPick a guest language. Each version exports the same WebAssembly function.
Create guest/add.wat:
wat
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))Build it:
sh
wat2wasm guest/add.wat -o module.wasmCreate main.go:
go
package main
import (
"context"
"fmt"
"log"
"os"
"slices"
"github.com/wago-org/wago"
)
func run(ctx context.Context) error {
wasm, err := os.ReadFile("module.wasm")
if err != nil {
return err
}
runtime := wago.NewRuntime()
defer runtime.Close()
module, err := runtime.Compile(wasm)
if err != nil {
return err
}
defer module.Close()
instance, err := runtime.Instantiate(ctx, module)
if err != nil {
return err
}
defer instance.Close()
// TinyGo reactors initialize their runtime through this export. WAT and
// AssemblyScript modules in this example do not emit it.
if slices.Contains(module.Exports(), "_initialize") {
if _, err := instance.InvokeValues(ctx, "_initialize"); err != nil {
return err
}
}
results, err := instance.InvokeValues(
ctx,
"add",
wago.ValueI32(20),
wago.ValueI32(22),
)
if err != nil {
return err
}
fmt.Println(results[0].I32())
return nil
}
func main() {
if err := run(context.Background()); err != nil {
log.Fatal(err)
}
}Run it:
sh
go run .text
42The guest language changes how you produce module.wasm, not how you embed it. The Go side sees the same WebAssembly types and export name in every case. TinyGo's _initialize export is the one lifecycle difference in this example; call it once per new instance before calling your own exports.
Know what you own
Runtime owns shared compiler resources, plugins, and lifecycle state. Module holds validated native code that can be reused. Instance owns mutable guest state such as memory, tables, and globals.
Create the runtime and compile the module once. Create instances according to the lifetime of the guest state you need, and close all three resources when you are finished.
From here, choose the part your application needs:
- Calls and state for values, memory, globals, and instance lifetime.
- Host functions when Wasm needs to call your Go code.
- Limits and cancellation before running untrusted modules.
- Concurrency and shutdown for a long-lived service.
- Precompiled artifacts when startup compilation is too expensive.
