Transports & runtimes
The same registered functions serve every transport; only the call at the bottom of main changes.
Rust also has a target the other SDKs do not: compiled to wasm32, a worker runs inside the
browser page, next to a DuckDB-WASM engine.
The four native transports
Section titled “The four native transports”| Call | Transport | When |
|---|---|---|
Worker::run() | stdin/stdout, or whatever argv asks for | The default. DuckDB spawns the process. |
transport::serve_unix | AF_UNIX socket | A long-lived warm worker, reused across calls. |
transport::serve_tcp | Raw Arrow IPC over TCP | A co-located sidecar on a trusted network. |
transport::serve_http | HTTP | Remote, shared, or load-balanced. |
run() is the one to reach for first: it parses the launcher’s arguments out of argv itself and
dispatches accordingly, so a single entry point covers stdio and the socket transports without a
flag of your own. That is what makes the tutorial’s ATTACH … LOCATION './target/release/calc' work
with no arguments at all.
transport-http is in the crate’s default features, so serve_http is there unless you asked for
it not to be. It pulls tokio and the vgi-rpc HTTP stack, neither of which compiles to wasm — so a
wasm build wants default-features = false, and the HTTP transport is the thing it gives up. The
same applies to sqlite, the other default feature, which compiles C.
In the browser, next to DuckDB-WASM
Section titled “In the browser, next to DuckDB-WASM”This is the Rust-specific one. Compiled to wasm32, a worker is served over DuckDB-WASM’s
SharedArrayBuffer channel and runs in the same page as the engine — no process, no socket, no
network.
The glue that makes that work is a fixed set of C-ABI exports the page-side boot script calls, and
it is identical for every worker. The wasm_worker! macro generates them from a builder you supply:
fn build() -> vgi::Worker {
let mut w = vgi::Worker::new();
w.register_scalar(Double);
w.set_catalog(vgi::catalog::CatalogModel {
name: "calc".to_string(),
..Default::default()
});
w
}
vgi::wasm_worker! { build = build }
The macro expands to nothing off wasm32, so a crate can invoke it unconditionally and still
build a normal native binary from the same source.
The channel is a SharedArrayBuffer, which browsers only hand out to a cross-origin-isolated page —
Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. Without
those headers the boot script has nothing to allocate and the worker never starts. That is a
deployment property of your page, not something the worker can arrange for itself.
Which one to pick
Section titled “Which one to pick”- Shipping a binary to a team —
run(), stdio. Nothing to operate. - The same worker called constantly — the Unix socket transport keeps one warm process instead of paying spawn cost per query.
- A shared service — HTTP, so the worker outlives any one client and can sit behind a load balancer.
- A browser app with DuckDB-WASM — the wasm32 target, so the whole thing runs client-side with no server at all.
Next steps
Section titled “Next steps”- What a worker exposes → Expose a catalog.
- Exact signatures → Worker & serving.