duckfn
Write DuckDB extensions in plain Rust.
duckfn is a Rust 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, a type
cast, or a nested type — no C/C++ glue code, and no local DuckDB build required.
- Repository: https://github.com/shijianjs/duckfn
- Crates:
duckfn·duckfn-macro - Built on:
quack-rs·libduckdb-sys - No
unsafeto write: nounsafe fn, no raw pointers in your function bodies - No DuckDB build required, no C/C++ code
- Attribute-driven registration through
inventory - Panic-safe on native: Rust panics become DuckDB errors instead of unwinding across the FFI boundary. (On the browser this guard cannot work — a
panic!cannot unwind across the JS boundary and surfaces as a stack overflow — so report errors withErr(duck_error(..)), notpanic!.) - Host file system access (native): read and write through DuckDB's virtual file system (
s3://,http(s)://withhttpfs, in-memory) from any callback — aggregate functions included — plus one-line helpers such asduckfn::duck_vfs::read_string/write_string/append_string(theowned-connectionfeature; unreliable under DuckDB-Wasm, so it is not inall— see the feature notes below) - Interop with other crates: the time wrapper types (
DuckDate,DuckTimestamp/_S/_Ms/_Ns,DuckTimestampTz,DuckTime) convert to and fromchrono,DuckUuid↔uuid, andDuckDecimal<W, S>↔rust_decimal(chrono/uuid/rust_decimalfeatures) — out-of-range values, DuckDB'sinfinityand digit-losing conversions come back as errors, never as a panic or a silent truncation - Function documentation:
description/comment/exampleon any#[duck_*]attribute, exported to thefunction_descriptions.csvthat DuckDB's community-extension pages read (clifeature) - Works with DuckDB's official multi-platform extension CI
Status: early / experimental. APIs may change before
1.0.
Repository layout
The repository is a Cargo workspace whose root is also the duckfn crate root:
| Path | Description | Published to crates.io |
|---|---|---|
/ (duckfn) |
Runtime framework: traits, type adapters, function registration. Also the workspace root. | Yes |
test/extension/, test/sql/ |
The example extension (duckfn): runnable SQL for every feature, its three entry points (src/lib.rs, src/bin/duckfn.rs) and its sqllogictest suite. Part of this package, compiled only with the quack feature. |
Yes — sources only, never compiled for a dependent |
duckfn-macro/ |
Procedural macros: #[duck_scalar_function], #[derive(DuckStruct)], #[derive(DuckEnum)], ... |
Yes |
docs/ |
Docusaurus documentation site: docs/docs/** (English) and docs/i18n/zh-Hans/** (Simplified Chinese). |
No, docs site — but its sources ship inside the duckfn package |
The published duckfn package carries the runtime, its tests, this README, the license, the whole
documentation source and the example extension with its sqllogictest suite, so the guide, the example
page and a complete worked extension are readable without cloning the repository
(cargo package --list in the repository root prints the exact file list). The example is compiled
only when the quack feature is on — off by default — so a dependency on duckfn compiles none of
it. Keeping the example inside this package is also what lets cargo ship it at all: cargo never
packages a subdirectory that contains its own Cargo.toml.
Installation
[]
= "0.0.18"
# duckfn itself is built on these two crates; add them explicitly when you use
# their types or builders directly.
= "0.16.0"
# `loadable-extension` dispatches through DuckDB's API table instead of linking
# libduckdb, which is what keeps a local DuckDB build unnecessary.
# (headers only — no linked library)
= { = ">=1.4.4, <2", = ["loadable-extension"] }
If you prefer the macros without the runtime, depend on
duckfn-macro directly; otherwise the macros are
re-exported by duckfn and no extra dependency is needed.
duckfn has seven optional features. cli adds the command-line tool that exports a
function_descriptions.csv for DuckDB's community-extension pages
(cargo run --features quack --bin duckfn-cli -- function_descriptions in this repository); only an
extension project's src/bin/duckfn.rs needs it. duckdb-1-5 enables what DuckDB's 1.5 C API
added: the logical types from DuckDB 1.5 (currently TIME_NS) and copy functions. owned-connection
builds on it to add host file-system access (duckfn::duck_vfs): read/write through DuckDB's VFS
from any callback, aggregates included. The other three are interop and independent of each other:
chrono converts the time wrapper types to and from chrono,
uuid converts DuckUuid to and from uuid, and rust_decimal
converts DuckDecimal<W, S> to and from rust_decimal — so
the epoch / 128-bit / scaled-integer arithmetic lives in duckfn rather than in every extension:
= { = "0.0.18", = ["duckdb-1-5", "owned-connection", "chrono", "uuid", "rust_decimal"] }
all is the aggregate switch — every optional feature except owned-connection. duckfn normally
sits at the end of the dependency tree, so features = ["all"] is the convenient spelling. The host
file system is left out of all on purpose: it is unreliable under the browser / DuckDB-Wasm build
(see the Troubleshooting / file-system docs), and on native DuckDB's own readers or the standard
library cover it, so it is opt-in via owned-connection rather than charged to everyone. Pick
individual features when you want a leaner tree.
quack is the one feature that is not meant for dependents: it compiles this package's own example
extension (test/extension/), and depends on all plus owned-connection (the example demonstrates
every capability). Asking for all never drags the example or its test functions into your build.
Quick start
use ;
/// ```sql
/// SELECT double_it(21); -- 42
/// SELECT double_it(NULL); -- NULL
/// SELECT double_it(13); -- error: unlucky input
/// ```
// Generate the extension entry point (name must be lowercase + underscores).
duckfn_entrypoint!;
The wrapper, the logical types and the registration are all generated, so the snippet above is
entirely safe Rust. The only place unsafe shows up is manual registration:
#[duck_custom_register] calls quack-rs' unsafe fn register_scalar / register_aggregate /
register_table.
Calling the generated module of a #[duck_scalar_function] exposes builders such as
scalar_function_builder() and scalar_overload_builder(), so you can register overloads or
function sets yourself.
Documentation
The full guide — every attribute and its arguments, the type mapping, the error model, and a runnable example extension — lives at https://shijianjs.github.io/duckfn/:
| Page | Contents |
|---|---|
| Introduction | What duckfn is, and how the crates fit together. |
| Create a project | Start from the duckfn extension template or DuckDB's official Rust one. |
| Project structure | The crate roots, error[E0583], and the command-line tool's own root. |
| Installation | Dependencies, MSRV, and why no DuckDB build is needed. |
| Quick start | Write, build and load your first extension. |
| Attributes | All attributes, shared arguments, and manual registration. |
| Scalar functions | Return shapes, NULL handling, batch mode, overloads. |
| Aggregate functions | Row handlers, state types, parallel aggregation. |
| Table functions | Row structs, named parameters, streaming. |
| Copy functions | Custom file formats for COPY ... TO / COPY ... FROM, built on runtime dynamic columns. |
| Type casts | Overriding CAST for one source/target pair. |
| Replacement scans | Making SELECT * FROM 'data.points' work. |
| SQL macros | Macros from Rust or from .sql files. |
| Type mapping | DuckDB ↔ Rust types, nullability and known gaps. |
| Community extension docs | The description / comment / example attributes, and the CSV DuckDB's community-extension pages read. |
| Errors and panics · Architecture | Error handling, expansion, registration and adapters. |
| Example extension | duckfn, the example shipped with this package, with runnable SQL for every feature. |
| Build and release · Contributing · FAQ | Local builds, CI, and troubleshooting. |
中文文档:https://shijianjs.github.io/duckfn/zh-Hans/
Rust API reference: https://docs.rs/duckfn
License
Licensed under the MIT License.