Introduction
Write DuckDB extensions in plain Rust.
duckfn is a framework for building DuckDB extensions on top of DuckDB's
C Extension API. A single attribute turns an ordinary Rust function into a DuckDB scalar,
aggregate, table or copy function, a SQL macro, a replacement scan, or a type cast.
use duckfn::{duck_error, duck_scalar_function, duckfn_entrypoint, DuckOptionResult};
/// ```sql
/// SELECT double_it(21); -- 42
/// SELECT double_it(NULL); -- NULL
/// SELECT double_it(13); -- error: unlucky input
/// ```
#[duck_scalar_function]
pub fn double_it(v: Option<i64>) -> DuckOptionResult<i64> {
if v == Some(13) {
return Err(duck_error("unlucky input"));
}
Ok(v.map(|x| x * 2))
}
// Generates the symbol DuckDB looks for when loading the extension.
duckfn_entrypoint!("my_ext");
The attribute generates the FFI wrapper, the column readers and writers, and the registration code, so everything above is safe Rust. One attribute is the whole pipeline:
Why duckfn
| No C/C++ glue code | DuckDB C types never appear in your code. You write Option<i64>, Vec<String>, #[derive(DuckStruct)] structs and #[derive(DuckEnum)] enums. |
| No local DuckDB build | The extension is compiled against headers only and dispatches through DuckDB's API table at load time. |
| Safe by default | No unsafe fn and no raw pointers in your function bodies. The only unsafe left is in explicit manual registration. |
| Attribute-driven registration | Annotating a function is enough; registration items are collected with inventory and applied when DuckDB loads the extension. |
| Panic-safe | A Rust panic inside a function body is caught and reported as a DuckDB error instead of unwinding across the FFI boundary. |
| Nested types included | Vec<T>, IndexMap<K, V>, fixed-size arrays, STRUCTs and any nesting of them map to DuckDB's LIST, MAP, ARRAY and STRUCT. |
| Fits DuckDB's CI | The repository reuses DuckDB's official multi-platform extension pipeline, so a version tag produces binaries for every supported platform. |
Same functions, two ways puts four functions from DuckDB's official
template and the quack-rs example next to their raw implementations.
How the pieces fit together
| Crate | Role |
|---|---|
duckfn | Runtime framework: traits, type adapters, value types, registration. Re-exports every macro. |
duckfn-macro | The procedural macros behind #[duck_scalar_function], #[derive(DuckStruct)], … |
quack-rs | The DuckDB C API bindings duckfn builds on; its builders and value types are part of the public surface. |
libduckdb-sys | DuckDB headers; with the loadable-extension feature nothing is linked at build time. |
Status
Early / experimental — APIs may change before
1.0.
Where to go next
- Create a project — start from the duckfn extension template, or from DuckDB's official Rust one.
- Installation — dependencies, MSRV, and why no DuckDB build is needed.
- Quick start — build and load your first extension.
- Attributes — the full attribute and argument reference.
- The example extension — a working extension covering every feature.