Installation
This page covers the Cargo.toml dependencies and release-profile settings a DuckDB
loadable extension built with quack-rs needs, the minimum Rust version, and the optional
test features.
Adding quack-rs to an existing extension
Add the following to your extension's Cargo.toml:
[dependencies]
quack-rs = "0.18"
libduckdb-sys = { version = ">=1.4.4, <2", features = ["loadable-extension"] }
Why
>=1.4.4, <2? Every DuckDB 1.4.x and 1.5.x release loads extensions built for C API versionv1.2.0(1.5.6 declaresv1.5.6and accepts every earlier one), soquack-rssupports both with a single bounded range. The<2upper bound prevents silent adoption of a future major release whose C API may change in breaking ways — making any such upgrade an explicit, auditable decision. See Extension Anatomy.
Required Cargo.toml settings
Every DuckDB extension requires specific Cargo settings to link and behave correctly:
[lib]
name = "my_extension" # ← must match extension name exactly (Pitfall P1)
crate-type = ["cdylib", "rlib"]
# ^^^^^^ cdylib produces the .so/.dylib/.dll DuckDB loads
# rlib optional: lets doctests, examples and tests/ link the crate
[profile.release]
panic = "unwind" # REQUIRED — quack-rs catches panics at every FFI boundary;
# "abort" makes that impossible (see below)
lto = true # recommended — reduces binary size, improves performance
opt-level = 3 # recommended
codegen-units = 1 # recommended — better optimisation, slower build
strip = true # recommended — reduces binary size
Why panic = "unwind", not "abort"?
Every callback quack-rs generates (the *_callback! macros, the closure-based builders,
FfiState's callbacks), and every entry point, runs your code inside
std::panic::catch_unwind and turns a panic into an ordinary SQL error that DuckDB
reports to the user. catch_unwind can only catch a panic that unwinds: under
panic = "abort" the process terminates at the panic site, before any guard runs, taking
the user's whole DuckDB session with it.
A raw unsafe extern "C" fn that you pass to a builder yourself (such as double_it in the
Quick Start) is installed as written, with no guard. Define it with the
matching macro (scalar_callback!, aggregate_update_callback!, …), which reports a panic
as a SQL error, or wrap its body in quack_rs::callback::catch_ffi_panic and report the
Err it returns yourself.
(A panic that escapes an extern "C" function without being caught is not undefined
behaviour on Rust ≥ 1.81 — the runtime aborts the process — but that is exactly the
outcome the guards exist to prevent.)
validate_release_profile rejects panic = "abort", and the scaffold generator emits
panic = "unwind".
Minimum Supported Rust Version
quack-rs requires Rust ≥ 1.86.0.
1.86.0 is a ceiling as much as a floor. DuckDB's community-extension build
workflow (_extension_distribution.yml in duckdb/extension-ci-tools) pins
Rust 1.86.0 for its WebAssembly jobs, so an extension — and therefore
quack-rs — must build on it; CI's msrv-vs-duckdb-ci job re-derives that pin
and fails if the MSRV rises above it. The msrv job checks the crate with
cargo +1.86.0 check, and the benchmark dev-dependency (criterion) needs
1.86 as well.
Install or update via:
rustup update stable
rustup default stable
Verify:
rustc --version # must be ≥ 1.86.0
Development dependencies
To run SQL against your functions inside cargo test, enable one of quack-rs's test
features as a dev-dependency:
[dev-dependencies]
# Compiles DuckDB from C++ source: no setup, slow cold build.
quack-rs = { version = "0.18", features = ["bundled-test"] }
# ...or link a prebuilt libduckdb instead (set DUCKDB_DOWNLOAD_LIB=1 or DUCKDB_LIB_DIR):
# quack-rs = { version = "0.18", features = ["bundled-test-prebuilt"] }
Either one initialises the loadable-extension dispatch table from the linked DuckDB, so
testing::InMemoryDb works and the whole C API — including your own registration code — can
be exercised in a test. Without them, any duckdb_* call in a cargo test process panics,
because nothing has filled the dispatch table. See the Testing Guide.
Starting a new extension from scratch
Use the scaffold generator to produce a complete project with these settings, the build files and CI already in place.