Vector Gateway Interface for Rust

Extend DuckDB with functions written in Rust — backed by Apache Arrow, callable straight from SQL.
The vgi crate implements the Vector Gateway Interface (VGI) for Rust: you write a small worker
exposing typed functions, and DuckDB calls them as if they were native. Data moves as Apache Arrow
record batches on arrow-rs, so it stays columnar across the
boundary — no row-by-row marshalling.
No C++ extension to compile, no linking against DuckDB, no version coupling. cargo build, ship the
binary.
Install
Section titled “Install”cargo add vgi arrow-array arrow-schema
Requires Rust 1.97+ and arrow-rs 59. A worker is an ordinary binary crate:
use vgi::catalog::CatalogModel;
use vgi::function::{ArgSpec, FunctionMetadata, ProcessParams, ScalarFunction};
use vgi::{Result, RpcError};
struct Double;
impl ScalarFunction for Double {
fn name(&self) -> &str { "double" }
fn metadata(&self) -> FunctionMetadata { /* … */ }
fn argument_specs(&self) -> Vec<ArgSpec> {
vec![ArgSpec::column("n", 0, "int64", "Value to double")]
}
fn process(&self, params: &ProcessParams, batch: &RecordBatch) -> Result<RecordBatch> {
/* … one output value per input row … */
}
}
fn main() {
let mut worker = vgi::Worker::new();
worker.register_scalar(Double);
worker.set_catalog(CatalogModel { name: "calc".to_string(), ..Default::default() });
worker.run();
}
ATTACH 'calc' (TYPE vgi, LOCATION './target/release/calcscalar');
SELECT calc.double(21); -- 42
Start here
Section titled “Start here”Three things to know up front
Section titled “Three things to know up front”- A trait is the function. No macro, no derive, no registry attribute — implement
ScalarFunction(or one of its four siblings) and hand the value toWorker::register_*. - Shared function, per-scan producer. A
TableFunctionisSend + Syncand immutable; the cursor that walks a scan is a separateTableProducerbuilt per execution. That split is why the function itself never needs a lock. - It runs where you need it. The same functions serve stdio, a Unix socket, TCP or HTTP — and
compiled to
wasm32, the whole worker runs inside the browser page next to a DuckDB-WASM engine. See Transports & runtimes.
Coming from another SDK? The protocol is identical and a Rust worker is wire-compatible with a Python, Go or TypeScript one — the same DuckDB extension drives all of them, so the concepts carry over unchanged.