witgo генерирует типизированный Go API из WIT-контракта для обеих сторон
Component Model. Go-host может загружать WebAssembly Component-плагины,
вызывать их exports и предоставлять host-функции, а guest-режим позволяет
реализовать и собрать сам плагин на Go/TinyGo. Один и тот же WIT import может
обслуживаться Go-кодом или другим зарегистрированным WebAssembly Component без
изменения consumer-кода.
Статус: beta. Библиотека уже покрывает основной сценарий Component Model, строгую проверку контракта до запуска, version handshake с Rust bridge и end-to-end тесты для сложных типов. Перед production-использованием всё равно стоит прогнать свои контракты и плагины отдельными интеграционными тестами.
- Go 1.18 или новее;
- плагин в формате WebAssembly Component (
.wasm), а не core Wasm module; - встроенный native bridge уже лежит в модуле для Linux, macOS и Windows на
amd64иarm64, отдельная установка при запуске не нужна.
Для написания Go guest-плагинов дополнительно нужны wit-bindgen-go, пакет
go.bytecodealliance.org/cm и TinyGo с target wasip2. Они не нужны обычному
host-приложению.
Parser, generator и generated packages также компилируются на остальных Go targets. Запуск Component зависит от native loader и Rust/Wasmtime bridge для выбранной платформы.
go get github.com/slavkiy/witgoНапример, wit/plugin.wit:
package example:plugins@1.0.0;
interface metadata {
record info {
name: string,
version: string,
description: string,
author: string,
license: string,
}
get: func() -> info;
}
interface host {
process-string: func(value: string) -> string;
}
world plugin {
import host;
export metadata;
}import host описывает функции, которые даёт приложение.
export metadata описывает функции, которые реализует плагин.
Создайте generate.go:
//go:build ignore
package main
import (
"log"
"github.com/slavkiy/witgo"
)
func main() {
err := witgo.GeneratePackage(witgo.Config{
Output: "./internal/contract",
Package: "contract",
}, "./wit")
if err != nil {
log.Fatal(err)
}
}Запустите:
go run generate.goБудет создан internal/contract/bindings.gen.go с типами Info, Host,
Plugin, PluginImports, а также helper-функциями PluginPing,
ValidatePlugin, CheckPlugin, OpenPlugin и OpenPluginWithOptions.
Это host-режим по умолчанию. Для кода самого плагина задайте
Mode: witgo.GenerateGuest и World: "plugin". В таком package будут
MetadataGuest, PluginGuest, ExportPlugin и Imports.Host, но не будет
host-only функций OpenPlugin, ValidatePlugin и composition API. Полный
пример находится в руководстве по Go guest-плагинам.
Для одного файла используйте GenerateFile, для явно выбранного набора файлов
одного package - GenerateFiles, для рекурсивного дерева одного package -
GenerateTree. Старый Generate(Config{WIT: ...}) сохранён для совместимости.
Стандартный WIT можно дополнить отдельным plugin.witgo.yaml. Например, для
package example:users@1.0.0 и alias timestamp в interface users WIT s64
может выглядеть в публичном Go API как time.Time, оставаясь s64 в Component
ABI:
version: 1
types:
example:users/users@1.0.0#timestamp:
go_type: time.Time
import: time
codec: unix-secondsOverlay подключается через Config.GoOverlay; без него generated output и
runtime behavior остаются прежними. Полный формат описан в
docs/go-overlays.md.
type pluginHost struct{}
func (pluginHost) ProcessString(_ context.Context, value string) (string, error) {
return "HOST:" + value, nil
}Go-компилятор сам проверит, что реализация соответствует generated interface
contract.Host.
package main
import (
"context"
"fmt"
"log"
contract "example.com/myapp/internal/contract"
)
func main() {
ctx := context.Background()
report, err := contract.ValidatePluginContext(ctx, "./plugins/plugin.component.wasm")
if err != nil {
log.Fatal(err)
}
if !report.Compatible {
log.Fatalf("incompatible plugin: %+v", report)
}
plugin, err := contract.OpenPluginContext(ctx, "./plugins/plugin.component.wasm", contract.PluginImports{
Host: pluginHost{},
})
if err != nil {
log.Fatal(err)
}
defer plugin.Close()
info, err := plugin.Metadata.Get(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(info.Name)
fmt.Println(info.Version)
fmt.Println(info.Author)
}Вызовы export-функций остаются типизированными:
info, err := plugin.Metadata.Get(ctx)Для нескольких плагинов удобнее один раз описать host policy. Нулевая policy безопасна: она запрещает ambient capabilities и загрузку зависимостей.
policy := witgo.HostPolicy{
Public: witgo.PluginGrant{
// Эти разрешения и лимиты получит каждый плагин.
Permissions: witgo.Permissions{
System: true,
Allow: []string{"example:plugins/host@1.0.0"},
},
Limits: witgo.PluginLimits{
FuelPerCall: 1_000_000,
Timeout: 2 * time.Second,
MemoryLimitBytes: 64 << 20,
},
},
Plugins: map[string]witgo.PluginGrant{
"downloader": {
Permissions: witgo.Permissions{Network: true},
},
"orchestrator": {
Permissions: witgo.Permissions{LoadPlugin: true},
AllowedPluginRoots: []string{"./plugins"},
},
},
}
plugin, err := contract.OpenPluginWithPolicyContext(
ctx, policy, "downloader", "./plugins/downloader.wasm",
contract.PluginImports{Host: pluginHost{}},
)System, Network и Files разрешают соответствующие WASI namespaces,
Allow принимает точные WIT interface/function patterns, а Deny всегда
имеет приоритет. LoadPlugin разрешает только зависимости из plugin manifest
и ограничивается host-owned AllowedPluginRoots.
Если plugin умеет запрашивать дополнительное топливо через opt-in runtime API,
хост также задаёт FuelRequests и FuelPolicy; сам plugin не может назначить
себе fuel или расширить права.
Низкоуровневый вариант через RuntimeOptions тоже сохранён:
plugin, err := contract.OpenPluginWithOptionsContext(
ctx,
"./plugins/plugin.component.wasm",
witgo.RuntimeOptions{
FuelPerCall: 1_000_000,
Timeout: 2 * time.Second,
MemoryLimitBytes: 64 << 20,
MaxResultBytes: 1 << 20,
InstanceLimit: 8,
},
contract.PluginImports{Host: pluginHost{}},
)Один вызов может сначала создать guest bindings из того же WIT-контракта, а затем собрать готовый Component и встроить manifest зависимостей:
err := witgo.BuildPlugin(witgo.PluginBuildConfig{
Generate: witgo.Config{
WIT: "./wit", WITMode: witgo.WITInputPackage,
World: "plugin", Output: "./internal/contract",
Package: "contract",
},
Build: witgo.GuestBuildConfig{
Main: "./cmd/plugin", World: "plugin",
WITPackage: "./wit",
Output: "./dist/plugin.component.wasm",
Manifest: &witgo.PluginManifest{Dependencies: map[string]string{
"example:plugins/cache@1.0.0": "cache.component.wasm",
}},
},
})В коде плагина остаётся только реализовать generated <Interface>Guest и
вызвать Export<World>; собранный файл затем открывается generated host API.
Если подробный отчёт не нужен, используйте короткую проверку:
if err := contract.CheckPlugin("./plugins/plugin.component.wasm"); err != nil {
log.Fatal(err)
}Ошибка от CheckPlugin поддерживает errors.Is(err, witgo.ErrContractMismatch)
и errors.As к *witgo.ContractValidationError.
- runtime работает in-process через доверенный Rust bridge, а не через sidecar;
- перед запуском всегда проверяются contract manifest и version handshake;
- вложенные плагины связываются через WIT imports/exports, но не получают прямой доступ к registry, runtime options или чужим runtime;
- системный vendor API
witgo:runtime/runtime@1.0.0подключается только явно черезEnableRuntimeAPIи даёт guest-коду только локальное состояние вызова; - запрос дополнительного fuel возможен только через
UnsafeRequestAdditionalFuel, а решение всегда остаётся за host policy; resource,future,streamиerror-contextпередаются как runtime-boundwitgo.Handleи не могут безопасно мигрировать между независимыми runtime-box;- обычный Go использует
puregoбез CGO на desktop Linux/macOS/Windows; - TinyGo native runtime поддерживается на Linux и Windows с CGo; TinyGo 0.41 на
macOS компилирует API и generator, но не может связать
dlopenbackend.
Ключевые ограничения и поведение собраны в docs/capabilities.md, архитектура и модель доверия - в docs/architecture.md.
Runtime покрывает bool, все WIT-числа, char, string, records, lists,
options, results, tuples, maps, enums, flags и variants.
resource, future, stream и error-context передаются как witgo.Handle.
Такой handle привязан к Store runtime-коробки, его можно вернуть обратно в
Component, передать вложенному WebAssembly provider в той же коробке и явно
закрыть через Handle.Close. Между независимыми коробками handle не копируется.
go run ./examples/contracts/basic
go run ./examples/scenarios/serverОжидаемый вывод:
Plugin metadata
Name: HOST:image-resizer
Version: 1.4.0
Author: Example Team
Description: Resizes uploaded images and creates previews.
witgo загружает version-matched Wasmtime shared library прямо в Go-процесс.
Нет отдельного дочернего процесса, нет stdin/stdout IPC, нет download шага во
время запуска. Нативная библиотека уже встроена в Go-модуль и при первом
использовании распаковывается в content-addressed cache после SHA-256
проверки.
При инициализации Go и Rust обмениваются protocol_version,
witgo_version, bridge_version, wasmtime_version и обязательными
feature-флагами. До запуска start bridge отвечает на contract ping
отсортированными именами import/export-функций, а generated Go bindings
сравнивают их с ожидаемым контрактом и зарегистрированными host imports.
Подробности:
- Оглавление документации
- Tutorial: Rust Component + Go host
- Go WASM plugin + Go host
- Архитектура runtime
- Troubleshooting
- Проверка контрактов
- Generated code
- Go type overlays
- Публичный API
- TinyGo и контекстный API
- Автоматические вложенные плагины
- Прозрачная композиция плагинов
- Migration guide
- Security model
- Релизный процесс
- Список изменений
go test ./...