Extension Anatomy
A DuckDB loadable extension is a shared library (.so / .dylib / .dll) that DuckDB loads
at runtime. Understanding what DuckDB expects makes every other part of quack-rs click.
The initialization sequence
When DuckDB loads your extension, it:
- Opens the shared library and looks up the symbol
{name}_init_c_api - Calls that function with an
infohandle and a pointer to function dispatch pointers - Your function must:
a. Call
duckdb_rs_extension_api_init(info, access, api_version)to initialize the dispatch table b. Get theduckdb_databasehandle viaaccess.get_database(info)c. Open aduckdb_connectionviaduckdb_connectd. Register functions on that connection e. Disconnect f. Returntrue(success) orfalse(failure)
quack_rs::entry_point::init_extension performs all of this correctly. The entry_point!
macro generates the required #[no_mangle] extern "C" symbol:
#![allow(unused)] fn main() { entry_point!(my_extension_init_c_api, |con| register(con)); // emits: #[no_mangle] pub unsafe extern "C" fn my_extension_init_c_api(...) }
Symbol naming
The symbol name must be {extension_name}_init_c_api — all lowercase, underscores only.
If the symbol is missing or misnamed, DuckDB fails to load the extension.
Extension name: "word_count_ext"
Required symbol: word_count_ext_init_c_api
Pass the full symbol name to entry_point!. This keeps the exported name explicit and
visible at the call site — no hidden identifier manipulation at compile time.
The loadable-extension feature
libduckdb-sys with features = ["loadable-extension"] changes how DuckDB API functions
work fundamentally:
Without feature: duckdb_query(...) → calls linked libduckdb directly
With feature: duckdb_query(...) → dispatches through an AtomicPtr table
The AtomicPtr table starts as null. DuckDB fills it in by calling
duckdb_rs_extension_api_init. This means:
- Any call before
duckdb_rs_extension_api_initpanics with"DuckDB API not initialized" - In
cargo test, you cannot call anyduckdb_*function — the table is never initialized
This is why quack-rs uses AggregateTestHarness for testing: it simulates the aggregate
lifecycle in pure Rust, with zero DuckDB API calls.
Dependency model
graph TD
EXT["your-extension"]
QR["quack-rs"]
LDS["libduckdb-sys >=1.4.4, <2<br/>{loadable-extension}<br/>(headers only — no linked library)"]
EXT --> QR
EXT --> LDS
QR --> LDS
The loadable-extension feature produces a shared library that does not statically link
DuckDB. Instead, it receives DuckDB's function pointers at load time. This is the correct
model for extensions: you run inside DuckDB's process, using its memory and threading.
Version support
libduckdb-sys = ">=1.4.4, <2" — the bounded range is intentional.
DuckDB 1.4.x and 1.5.x both expose C API version v1.2.0 (the version string embedded
in duckdb_rs_extension_api_init). quack-rs has been E2E tested against both releases.
Using a range rather than an exact pin means:
- Extension authors can choose their DuckDB target (pin to
=1.4.4or=1.5.0in their ownCargo.toml) and resolve cleanly againstquack-rs quack-rsitself doesn't force a DuckDB downgrade on users
The <2 upper bound is equally intentional: it prevents silent adoption of a future major
release that may introduce breaking C API changes. Upgrading beyond the 1.x band requires
an explicit quack-rs release that audits the new C API surface.
For your own extension's
Cargo.toml: pinlibduckdb-systo the exact DuckDB version you build and test against (e.g.,=1.5.0). Your extension binary will only load in the DuckDB version it was compiled for regardless — the range only matters forquack-rsitself as a library dependency.
Binary compatibility
Extension binaries are tied to a specific DuckDB version and platform. Key facts:
- An extension compiled for DuckDB 1.4.4 will not load in DuckDB 1.5.0
- DuckDB verifies binary compatibility at load time and refuses mismatched binaries
- Official DuckDB extensions are cryptographically signed; community extensions are not
- To load unsigned extensions:
SET allow_unsigned_extensions = true(development only) - The community extension CI provides automated cross-platform builds for each DuckDB release