This is the Golang implementation of the waPC standard for WebAssembly host runtimes. It allows any WebAssembly module to be loaded as a guest and receive requests for invocation as well as to make its own function requests of the host.
Example
The following is a simple example of synchronous, bidirectional procedure calls between a WebAssembly host runtime and the guest module.
package main import ( "context" "fmt" "os" "strings" "github.com/wapc/wapc-go" "github.com/wapc/wapc-go/engines/wazero" ) func main() { if len(os.Args) < 2 { fmt.Println("usage: hello <name>") return } name := os.Args[1] ctx := context.Background() guest, err := os.ReadFile("hello/hello.wasm") if err != nil { panic(err) } engine := wazero.Engine() module, err := engine.New(ctx, host, guest, &wapc.ModuleConfig{ Logger: wapc.PrintlnLogger, Stdout: os.Stdout, Stderr: os.Stderr, }) if err != nil { panic(err) } defer module.Close(ctx) instance, err := module.Instantiate(ctx) if err != nil { panic(err) } defer instance.Close(ctx) result, err := instance.Invoke(ctx, "hello", []byte(name)) if err != nil { panic(err) } fmt.Println(string(result)) } func host(ctx context.Context, binding, namespace, operation string, payload []byte) ([]byte, error) { // Route the payload to any custom functionality accordingly. // You can even route to other waPC modules!!! switch namespace { case "example": switch operation { case "capitalize": name := string(payload) name = strings.Title(name) return []byte(name), nil } } return []byte("default"), nil }
To see this in action, enter the following in your shell:
$ go run example/main.go waPC!
hello called
Hello, WaPC!
Alternatively you can use a Pool to manage a pool of instances.
pool, err := wapc.NewPool(ctx, module, 10, func(instance wapc.Instance) error { // Do something to initialize this instance before use. return nil }) if err != nil { panic(err) } defer pool.Close(ctx) for i := 0; i < 100; i++ { instance, err := pool.Get(10 * time.Millisecond) if err != nil { panic(err) } result, err := instance.Invoke(ctx, "hello", []byte("waPC")) if err != nil { panic(err) } fmt.Println(string(result)) err = pool.Return(instance) if err != nil { panic(err) } }
While the above example uses wazero, wapc-go is decoupled (via wapc.Engine) and can be used with different runtimes.
Engines
Here are the supported wapc.Engine implementations, in alphabetical order:
| Name | Usage | Build Tag | Package |
|---|---|---|---|
| wasmer-go | wasmer.Engine() |
wasmer | github.com/wasmerio/wasmer-go |
| wasmtime-go | wasmtime.Engine() |
wasmtime | github.com/bytecodealliance/wasmtime-go |
| wazero | wazero.Engine() |
N/A | github.com/tetratelabs/wazero |
For example, to switch the engine to wasmer, change example/main.go like below:
--- a/example/main.go +++ b/example/main.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/wapc/wapc-go" - "github.com/wapc/wapc-go/engines/wazero" + "github.com/wapc/wapc-go/engines/wasmer" ) func main() { @@ -22,7 +22,7 @@ func main() { panic(err) } - engine := wazero.Engine() + engine := wasmer.Engine() module, err := engine.New(ctx, code, hostCall) if err != nil {
Then, run with its build tag:
$ go run --tags wasmer example/main.go waPC! hello called Hello, WaPC!
Differences with wapc-rs (Rust)
Besides engine choices, there differences between this library and the Rust implementation:
- Separate compilation (
New) and instantiation (Instantiate) steps. This is to incur the cost of compilation once in a multi-instance scenario. Poolfor creating a pool of instances for a given Module.