WIT Primer
WIT (WebAssembly Interface Types) is the interface definition language of the WebAssembly Component Model. Think of it as the equivalent of Protocol Buffers for WebAssembly: it describes the functions, types, and error shapes that two sides of a boundary exchange, without prescribing the implementation language on either side.
For Torchsnap, WIT is the single source of truth for the host-gadget boundary. The host uses it to generate the Rust code that calls into gadgets. Gadgets use it (through the SDK or directly) to generate the code that implements those calls and reaches back into the host. Both sides agree on the same types, the same function signatures, and the same error variants because they are generated from the same file.
This page covers the WIT syntax itself and how bindings are generated from the contract. If you are already familiar with WIT and want to jump straight to the available APIs, see the Imports and Exports reference pages, which document every interface with its WIT definition, SDK helpers, and code examples.
Worlds
Section titled “Worlds”WIT organizes contracts into worlds. A world declares which interfaces the
component imports (calls into) and which it exports (implements). The Torchsnap
gadget contract defines a single world called gadget:
world gadget { // Host capabilities the gadget can call import logging; import clipboard; import sql-storage; // ... (14 imports total)
// Interfaces the gadget must implement export lifecycle; export search; export messaging; export tasks;}The import and export lines reference named interfaces defined elsewhere in
the same file. What each interface contains is covered in the sections below.
Interfaces and functions
Section titled “Interfaces and functions”An interface groups related functions and types. Functions are declared with
func, listing their parameters and return type:
interface clipboard { write-text: func(text: string) -> result<_, clipboard-error>;}This declares a clipboard interface with one function, write-text, that
takes a string and returns either success or a clipboard-error.
Records
Section titled “Records”A record is a named struct with typed fields:
record action { id: action-id, label: string,}Variants
Section titled “Variants”A variant is a tagged union. Each case can optionally carry data:
variant entry-icon { hero-icon(string), data-url(string), asset-icon(string), emoji(string),}Each case names the kind of icon and carries the value (a Heroicons name, a base64 data URL, a bundled asset path, or an emoji character).
An enum is a variant where no case carries data:
enum post-action { nothing, dismiss, keep-open }Resources
Section titled “Resources”A resource is an opaque handle with methods. The host creates it, the gadget
holds a reference and calls methods on it:
resource sql-handle { execute: func(sql: string, params: list<sql-value>) -> result<u64, string>; query: func(sql: string, params: list<sql-value>) -> result<list<list<sql-value>>, string>;}The gadget obtains an sql-handle by calling connection() and then uses
execute and query on it. The host manages the underlying database connection
behind the handle.
Built-in types
Section titled “Built-in types”WIT provides a set of generic types used throughout the contract:
option<T>: a value that may be absent (like Rust’sOptionor TypeScript’sT | undefined)result<T, E>: success or failure (like Rust’sResult)list<T>: a variable-length sequencetuple<T, U>: a fixed-size pair (or triple, etc.)string,bool,u8,u16,u32,u64,s32,s64,f64: scalar types
Cross-interface references
Section titled “Cross-interface references”The use keyword imports types from another interface so they can be shared:
interface search { use types.{entry-icon, action-id, action}; // ...}This allows search to reference entry-icon, action-id, and action
without redefining them. The types interface exists specifically to hold shared
type definitions used by both imports and exports.
Generating bindings
Section titled “Generating bindings”The WIT file is language-agnostic. Any language that can compile to
wasm32-wasip2 and produce a valid WebAssembly component can implement the
gadget contract. What differs between languages is how you turn the WIT
definitions into callable code.
Rust and the SDK
Section titled “Rust and the SDK”For Rust, the
wit-bindgen project provides
a procedural macro that reads the WIT file at compile time and generates Rust
traits, structs, and function stubs matching the contract. The
torchsnap-gadget-sdk crate runs this generation once:
// Inside torchsnap-gadget-sdk (gadgets/gadget-sdk/src/lib.rs)wit_bindgen::generate!({ path: "wit", world: "gadget", pub_export_macro: true, default_bindings_module: "::torchsnap_gadget_sdk",});This produces the four guest traits (LifecycleGuest, SearchGuest,
MessagingGuest, TasksGuest), all the WIT record and variant types
(CatalogEntry, ScoredEntry, SearchResponse, EntryIcon, etc.), and the
host import modules (clipboard, http, opener, and so on). The SDK
re-exports everything through its prelude, so your gadget code only needs:
use torchsnap_gadget_sdk::prelude::*;On top of the raw generated bindings, the SDK adds convenience helpers like typed settings access, SQL row parsing, and structured logging. These are covered in the Imports reference alongside the underlying WIT interfaces.
You do not need to run wit-bindgen yourself or reference the WIT file directly
when using the SDK. The bindings are pre-generated inside the crate.
Other languages
Section titled “Other languages”Full WIT contract
Section titled “Full WIT contract”The complete interface definition for the gadget world. This is the
source of truth for what gadgets must implement and what the host provides. The
canonical copy lives at
gadgets/gadget-sdk/wit/torchsnap-gadget.wit
in the Torchsnap repository.