Ergonomic bindings to duckdb for Rust
960
stars
992
commits
Rust
primary language
Sep 10, 2026
updated
duckdb-rs is an ergonomic Rust wrapper for DuckDB.
You can use it to:
Create a new project and add the duckdb crate:
cargo new quack-in-rust
cd quack-in-rust
cargo add duckdb -F bundled
Update src/main.rs with the following code:
use duckdb::{params, Connection, Result};
struct Duck {
id: i32,
name: String,
}
fn main() -> Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE ducks (id INTEGER PRIMARY KEY, name TEXT)",
[], // empty list of parameters
)?;
conn.execute_batch(
r#"
INSERT INTO ducks (id, name) VALUES (1, 'Donald Duck');
INSERT INTO ducks (id, name) VALUES (2, 'Scrooge McDuck');
"#,
)?;
conn.execute(
"INSERT INTO ducks (id, name) VALUES (?, ?)",
params![3, "Darkwing Duck"],
)?;
let ducks = conn
.prepare("FROM ducks")?
.query_map([], |row| {
Ok(Duck {
id: row.get(0)?,
name: row.get(1)?,
})
})?
.collect::<Result<Vec<_>>>()?;
for duck in ducks {
println!("{}) {}", duck.id, duck.name);
}
Ok(())
}
Execute the program with cargo run and watch DuckDB in action!
The following examples demonstrate various features and use cases of duckdb-rs:
vtab-arrow feature).vscalar feature).vtab feature).Run an executable example using your configured DuckDB library:
cargo run --example basic
Pass each example's required feature when applicable:
cargo run --example arrow_vtab --features vtab-arrow
cargo run --example vscalar --features vscalar
cargo run --example vtab --features vtab
cargo run --example parquet --features parquet
If you do not have a system DuckDB library and are not using DUCKDB_DOWNLOAD_LIB, add bundled to the feature list. For example:
cargo run --example basic --features bundled
cargo run --example arrow_vtab --features "bundled vtab-arrow"
hello-ext is a library target rather than an executable, so build it instead of using cargo run:
cargo build --example hello-ext --features loadable-extension
The duckdb crate provides a number of Cargo features that can be enabled to add functionality:
vtab - Base support for creating custom table functions and virtual tables.vtab-arrow - Apache Arrow integration for virtual tables. Enables conversion between Arrow RecordBatch and DuckDB data chunks.vtab-excel - Deprecated no-op retained for feature compatibility.vscalar - Create custom scalar functions that operate on individual values or rows.vscalar-arrow - Arrow-optimized scalar functions for vectorized operations. Requires vscalar.json - Enables reading and writing JSON files. Implies bundled.parquet - Enables reading and writing Parquet files. Implies bundled.These extensions are only available through the CMake build backend and imply bundled-cmake.
autocomplete - DuckDB's autocomplete extension.icu - DuckDB's ICU extension for locale-aware operations.tpch - DuckDB's TPC-H benchmark extension.tpcds - DuckDB's TPC-DS benchmark extension.appender-arrow - Efficient bulk insertion of Arrow data into DuckDB tables.polars - Integration with Polars DataFrames.rust_decimal - Compatibility impls for rust_decimal::Decimal. DuckDB
DECIMAL values use duckdb::types::Decimal as the core full-domain
carrier; this feature restores ToSql/FromSql conversions for
rust_decimal::Decimal when values fit that crate's decimal domain,
including compatibility reads from FLOAT, DOUBLE, and text columns.vtab-full - Enables virtual table features: vtab-arrow and appender-arrow; retains the deprecated vtab-excel compatibility flag.extensions-full - Enables all major extensions: json, parquet, and vtab-full.modern-full - Enables modern Rust ecosystem integrations: chrono, serde_json, url, r2d2, uuid, polars, and rust_decimal.bundled - Uses a bundled version of DuckDB's source code and compiles it during build. This is the simplest way to get started and avoids needing DuckDB system libraries.bundled-cmake - Experimental. Builds DuckDB via its upstream CMake build system instead of cc. Requires a duckdb-rs checkout (not available from crates.io). See step 2 below for details.buildtime_bindgen - Use bindgen at build time to generate fresh bindings instead of using pre-generated ones.loadable-extension - Experimental support for creating loadable DuckDB extensions. Includes procedural macros for extension development. Only enable this when building an extension, never for a client application - see Database client or extension?Starting with DuckDB v1.5.0, the duckdb-rs version encodes the DuckDB version in its second semver component.
The format is 1.MAJOR_MINOR_PATCH.x, e.g., DuckDB v1.5.0 maps to duckdb-rs 1.10500.x.
Use a tilde requirement to receive duckdb-rs patch releases without automatically moving to a different bundled DuckDB version.
The recommended way to use duckdb-rs is to add it from crates.io:
cargo add duckdb -F bundled
Or manually add it to your Cargo.toml:
[dependencies]
duckdb = { version = "~1.10505.0", features = ["bundled"] }
To use the latest development version from the main branch, you can specify a git dependency in your Cargo.toml:
# Use a specific branch
duckdb = { git = "https://github.com/duckdb/duckdb-rs", branch = "main", features = ["bundled"] }
# Use a specific commit
duckdb = { git = "https://github.com/duckdb/duckdb-rs", rev = "abc123def", features = ["bundled"] }
Note: Using the main branch of duckdb-rs means you'll get the latest Rust bindings and features, but you'll still be using whatever version of DuckDB core is bundled with that commit (when using the bundled feature).
Use the v1.4-andium branch for LTS releases that stay on DuckDB 1.4 Andium.
libduckdb-sys is a separate crate from duckdb-rs that provides the Rust
declarations for DuckDB's C API. By default, libduckdb-sys attempts to find a DuckDB library that already exists on your system using pkg-config, or a
Vcpkg installation for MSVC ABI builds.
You can adjust this behavior in a number of ways:
If you use the bundled feature, libduckdb-sys will use the
cc crate to compile DuckDB from source and
link against that. This source is embedded in the libduckdb-sys crate and
tracks the DuckDB version vendored for that duckdb-rs release.
This is probably the simplest solution to any build problems. You can enable this by adding the following in your Cargo.toml file:
cargo add duckdb --features bundled
Cargo.toml will be updated.
[dependencies]
duckdb = { version = "~1.10505.0", features = ["bundled"] }
If you use the bundled-cmake feature, libduckdb-sys will build DuckDB from the local checkout in crates/libduckdb-sys/duckdb-sources using upstream CMake. This keeps plain bundled unchanged while allowing CMake-only extensions such as icu.
Example:
[dependencies]
duckdb = { git = "https://github.com/duckdb/duckdb-rs", branch = "main", features = ["bundled-cmake", "icu"] }
Notes:
bundled-cmake is experimental and requires a git/workspace checkout. Published crates omit the full duckdb-sources tree.bundled for conditional-compilation gates, but uses CMake instead of the cc backend. Any CMake-only extension feature (e.g. icu) enables it automatically.core_functions and parquet) and therefore implies the parquet Cargo feature.bundled uses DuckDB's standard allocator and skips jemalloc. bundled-cmake enables upstream jemalloc on supported 64-bit, non-musl Linux targets. Other targets follow DuckDB's CMake checks. Set DUCKDB_DISABLE_JEMALLOC=1 to force the standard allocator.bundled, despite upstream CMake defaults. Set DUCKDB_DISABLE_EXTENSION_LOAD=1 or DISABLE_EXTENSION_LOAD=1 to disable external extension install/load support and force autoload/autoinstall off. Statically linked extensions remain available.ninja is on PATH, the build uses Ninja by default. Set CMAKE_GENERATOR to override.Release mode by default, even for Rust debug builds, avoiding DuckDB's much slower debug/sanitizer profile. Override with DUCKDB_CMAKE_BUILD_TYPE or CMAKE_BUILD_TYPE. DUCKDB_CMAKE_BUILD_TYPE takes precedence.DUCKDB_EXTENSION_CONFIGS is unsupported. Setting it fails fast.cargo build -vv -F bundled-cmake for CMake configure/build logs.When linking against a DuckDB library already on the system (so not using any of the bundled features), you can set the DUCKDB_LIB_DIR environment variable to point to a directory containing the library. You can also set the DUCKDB_INCLUDE_DIR variable to point to the directory containing duckdb.h.
Linux ARM64 example:
wget https://github.com/duckdb/duckdb/releases/download/v1.5.5/libduckdb-linux-arm64.zip
unzip libduckdb-linux-arm64.zip -d libduckdb
export DUCKDB_LIB_DIR=$PWD/libduckdb
export DUCKDB_INCLUDE_DIR=$DUCKDB_LIB_DIR
export LD_LIBRARY_PATH=$DUCKDB_LIB_DIR
cargo build --examples
For x86-64 Linux, replace arm64 with amd64 in the archive name and URL.
macOS example:
wget https://github.com/duckdb/duckdb/releases/download/v1.5.5/libduckdb-osx-universal.zip
unzip libduckdb-osx-universal.zip -d libduckdb
export DUCKDB_LIB_DIR=$PWD/libduckdb
export DUCKDB_INCLUDE_DIR=$DUCKDB_LIB_DIR
export DYLD_FALLBACK_LIBRARY_PATH=$DUCKDB_LIB_DIR
cargo build --examples
Setting DUCKDB_DOWNLOAD_LIB=1 makes the build script download pre-built DuckDB binaries from GitHub Releases. This links against the dynamic library in the archive and effectively automates the manual steps above. Leave DUCKDB_STATIC unset: setting it requests a static duckdb_static library, which the downloaded archives do not contain. The archives are cached in target/duckdb-download/<target>/<version> and that directory is automatically added to the linker search path. The downloaded version always matches the DuckDB version encoded in the libduckdb-sys crate version.
DUCKDB_DOWNLOAD_LIB=1 cargo test
Installing the DuckDB development packages will usually be all that is required, but
the build helpers for pkg-config
and vcpkg have some additional configuration
options. vcpkg-rs uses static libraries by default. To opt into dynamic
linking, set the VCPKGRS_DYNAMIC=1 environment variable before building.
When none of the options above are used, the build script falls back to this discovery path and will emit the appropriate cargo:rustc-link-lib directives if DuckDB is found on your system.
Cross-compiling with bundled is best-effort (not covered by CI): it compiles DuckDB from C++ source and so needs a working C++ cross-compiler for the target, not just the Rust toolchain. If you can't get one working, prefer one of the non-bundled options above (e.g. DUCKDB_LIB_DIR or DUCKDB_DOWNLOAD_LIB=1) and link a pre-built library for the target instead.
When using the bundled feature, the ICU extension is not included due to crates.io's 10MB package size limit. This means some date/time operations (like now() - interval '1 day' or ts::date casts) will fail. You can load ICU at runtime:
conn.execute_batch("INSTALL icu; LOAD icu;")?;
Alternatively, link against a system libduckdb that was compiled with ICU (see build instructions above).
If you are working from a duckdb-rs checkout, you can also use bundled-cmake,icu to compile ICU in through DuckDB's CMake build.
We use bindgen to generate the Rust
declarations from DuckDB's C header file. bindgen
recommends
running this as part of the build process for libraries that use it. We tried
this briefly (duckdb 0.10.0, specifically), but it had some annoyances:
libduckdb-sys (and therefore duckdb) increased
dramatically.bindgen requires a relatively recent version of Clang, which many
systems do not have installed by default.bindgen also requires the DuckDB header file to be present.So we try to avoid running bindgen at build time by shipping
pregenerated bindings for DuckDB.
If you use the bundled feature, you will get pregenerated bindings for the
bundled version of DuckDB. If you want to run bindgen at build time to
produce your own bindings, use the buildtime_bindgen Cargo feature.
A Connection is Send but not Sync: you may move it between threads, but you cannot share a single connection across threads at the same time. DuckDB does support concurrent access through multiple connections to the same database (see DuckDB's concurrency docs.
For multi-threaded applications, give each thread its own connection. The simplest way is a connection pool via the r2d2 feature:
use duckdb::{DuckdbConnectionManager, params};
let manager = DuckdbConnectionManager::file("file.db")?;
let pool = r2d2::Pool::new(manager)?;
// Each worker checks out its own connection from the pool.
let conn = pool.get()?;
conn.execute("INSERT INTO foo (bar) VALUES (?)", params![1])?;
duckdb-rs covers two distinct use cases, and you normally pick exactly one:
.duckdb_extension that DuckDB loads at runtime. Enable the loadable-extension feature.Do not enable loadable-extension for a regular client application. It replaces DuckDB's normal C API functions with extension wrappers that are only initialized when DuckDB loads your extension. Outside an extension context, calls such as Connection::open_in_memory() then panic with DuckDB API not initialized or DuckDB feature omitted.
The hello-ext example shows the extension API in action: it registers a hello(name) table function via the loadable-extension feature and the duckdb_entrypoint_c_api macro.
cargo build alone does not produce a loadable extension though. DuckDB only loads files ending in .duckdb_extension that carry a metadata footer matching the target DuckDB version. A bare shared library fails with The file is not a DuckDB extension. The metadata at the end of the file is invalid. Appending that footer requires DuckDB's extension build tooling.
So rather than wiring this up by hand, start from the official extension-template-rs template for a complete, ready-to-build Rust extension. It handles the metadata step, platform/version detection, tests, and CI. Its make debug builds the shared library and then appends the footer, producing a .duckdb_extension you load with duckdb -unsigned.
duckdb-rs is built and tested with stable Rust, and keeps a rolling MSRV (minimum supported Rust version) that may only be updated when the encoded DuckDB major/minor version changes, on a need basis (e.g. project dependencies bump their MSRV or a particular Rust feature is useful for us). The new MSRV will be at least 6 months old. DuckDB patch updates and crate patch releases (e.g. 1.10500.0 to 1.10500.1) are guaranteed to keep the same MSRV.
We welcome contributions! Take a look at CONTRIBUTING.md for more information.
Join our Discord to chat with the community in the #rust channel.
Copyright (c) Stichting DuckDB Foundation
Licensed under the MIT license.
(top 30 of 75)
Rust
99.1%
Ergonomic bindings to duckdb for Rust
960
stars
992
commits
Rust
primary language
Sep 10, 2026
updated
duckdb-rs is an ergonomic Rust wrapper for DuckDB.
You can use it to:
Create a new project and add the duckdb crate:
cargo new quack-in-rust
cd quack-in-rust
cargo add duckdb -F bundled
Update src/main.rs with the following code:
use duckdb::{params, Connection, Result};
struct Duck {
id: i32,
name: String,
}
fn main() -> Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE ducks (id INTEGER PRIMARY KEY, name TEXT)",
[], // empty list of parameters
)?;
conn.execute_batch(
r#"
INSERT INTO ducks (id, name) VALUES (1, 'Donald Duck');
INSERT INTO ducks (id, name) VALUES (2, 'Scrooge McDuck');
"#,
)?;
conn.execute(
"INSERT INTO ducks (id, name) VALUES (?, ?)",
params![3, "Darkwing Duck"],
)?;
let ducks = conn
.prepare("FROM ducks")?
.query_map([], |row| {
Ok(Duck {
id: row.get(0)?,
name: row.get(1)?,
})
})?
.collect::<Result<Vec<_>>>()?;
for duck in ducks {
println!("{}) {}", duck.id, duck.name);
}
Ok(())
}
Execute the program with cargo run and watch DuckDB in action!
The following examples demonstrate various features and use cases of duckdb-rs:
vtab-arrow feature).vscalar feature).vtab feature).Run an executable example using your configured DuckDB library:
cargo run --example basic
Pass each example's required feature when applicable:
cargo run --example arrow_vtab --features vtab-arrow
cargo run --example vscalar --features vscalar
cargo run --example vtab --features vtab
cargo run --example parquet --features parquet
If you do not have a system DuckDB library and are not using DUCKDB_DOWNLOAD_LIB, add bundled to the feature list. For example:
cargo run --example basic --features bundled
cargo run --example arrow_vtab --features "bundled vtab-arrow"
hello-ext is a library target rather than an executable, so build it instead of using cargo run:
cargo build --example hello-ext --features loadable-extension
The duckdb crate provides a number of Cargo features that can be enabled to add functionality:
vtab - Base support for creating custom table functions and virtual tables.vtab-arrow - Apache Arrow integration for virtual tables. Enables conversion between Arrow RecordBatch and DuckDB data chunks.vtab-excel - Deprecated no-op retained for feature compatibility.vscalar - Create custom scalar functions that operate on individual values or rows.vscalar-arrow - Arrow-optimized scalar functions for vectorized operations. Requires vscalar.json - Enables reading and writing JSON files. Implies bundled.parquet - Enables reading and writing Parquet files. Implies bundled.These extensions are only available through the CMake build backend and imply bundled-cmake.
autocomplete - DuckDB's autocomplete extension.icu - DuckDB's ICU extension for locale-aware operations.tpch - DuckDB's TPC-H benchmark extension.tpcds - DuckDB's TPC-DS benchmark extension.appender-arrow - Efficient bulk insertion of Arrow data into DuckDB tables.polars - Integration with Polars DataFrames.rust_decimal - Compatibility impls for rust_decimal::Decimal. DuckDB
DECIMAL values use duckdb::types::Decimal as the core full-domain
carrier; this feature restores ToSql/FromSql conversions for
rust_decimal::Decimal when values fit that crate's decimal domain,
including compatibility reads from FLOAT, DOUBLE, and text columns.vtab-full - Enables virtual table features: vtab-arrow and appender-arrow; retains the deprecated vtab-excel compatibility flag.extensions-full - Enables all major extensions: json, parquet, and vtab-full.modern-full - Enables modern Rust ecosystem integrations: chrono, serde_json, url, r2d2, uuid, polars, and rust_decimal.bundled - Uses a bundled version of DuckDB's source code and compiles it during build. This is the simplest way to get started and avoids needing DuckDB system libraries.bundled-cmake - Experimental. Builds DuckDB via its upstream CMake build system instead of cc. Requires a duckdb-rs checkout (not available from crates.io). See step 2 below for details.buildtime_bindgen - Use bindgen at build time to generate fresh bindings instead of using pre-generated ones.loadable-extension - Experimental support for creating loadable DuckDB extensions. Includes procedural macros for extension development. Only enable this when building an extension, never for a client application - see Database client or extension?Starting with DuckDB v1.5.0, the duckdb-rs version encodes the DuckDB version in its second semver component.
The format is 1.MAJOR_MINOR_PATCH.x, e.g., DuckDB v1.5.0 maps to duckdb-rs 1.10500.x.
Use a tilde requirement to receive duckdb-rs patch releases without automatically moving to a different bundled DuckDB version.
The recommended way to use duckdb-rs is to add it from crates.io:
cargo add duckdb -F bundled
Or manually add it to your Cargo.toml:
[dependencies]
duckdb = { version = "~1.10505.0", features = ["bundled"] }
To use the latest development version from the main branch, you can specify a git dependency in your Cargo.toml:
# Use a specific branch
duckdb = { git = "https://github.com/duckdb/duckdb-rs", branch = "main", features = ["bundled"] }
# Use a specific commit
duckdb = { git = "https://github.com/duckdb/duckdb-rs", rev = "abc123def", features = ["bundled"] }
Note: Using the main branch of duckdb-rs means you'll get the latest Rust bindings and features, but you'll still be using whatever version of DuckDB core is bundled with that commit (when using the bundled feature).
Use the v1.4-andium branch for LTS releases that stay on DuckDB 1.4 Andium.
libduckdb-sys is a separate crate from duckdb-rs that provides the Rust
declarations for DuckDB's C API. By default, libduckdb-sys attempts to find a DuckDB library that already exists on your system using pkg-config, or a
Vcpkg installation for MSVC ABI builds.
You can adjust this behavior in a number of ways:
If you use the bundled feature, libduckdb-sys will use the
cc crate to compile DuckDB from source and
link against that. This source is embedded in the libduckdb-sys crate and
tracks the DuckDB version vendored for that duckdb-rs release.
This is probably the simplest solution to any build problems. You can enable this by adding the following in your Cargo.toml file:
cargo add duckdb --features bundled
Cargo.toml will be updated.
[dependencies]
duckdb = { version = "~1.10505.0", features = ["bundled"] }
If you use the bundled-cmake feature, libduckdb-sys will build DuckDB from the local checkout in crates/libduckdb-sys/duckdb-sources using upstream CMake. This keeps plain bundled unchanged while allowing CMake-only extensions such as icu.
Example:
[dependencies]
duckdb = { git = "https://github.com/duckdb/duckdb-rs", branch = "main", features = ["bundled-cmake", "icu"] }
Notes:
bundled-cmake is experimental and requires a git/workspace checkout. Published crates omit the full duckdb-sources tree.bundled for conditional-compilation gates, but uses CMake instead of the cc backend. Any CMake-only extension feature (e.g. icu) enables it automatically.core_functions and parquet) and therefore implies the parquet Cargo feature.bundled uses DuckDB's standard allocator and skips jemalloc. bundled-cmake enables upstream jemalloc on supported 64-bit, non-musl Linux targets. Other targets follow DuckDB's CMake checks. Set DUCKDB_DISABLE_JEMALLOC=1 to force the standard allocator.bundled, despite upstream CMake defaults. Set DUCKDB_DISABLE_EXTENSION_LOAD=1 or DISABLE_EXTENSION_LOAD=1 to disable external extension install/load support and force autoload/autoinstall off. Statically linked extensions remain available.ninja is on PATH, the build uses Ninja by default. Set CMAKE_GENERATOR to override.Release mode by default, even for Rust debug builds, avoiding DuckDB's much slower debug/sanitizer profile. Override with DUCKDB_CMAKE_BUILD_TYPE or CMAKE_BUILD_TYPE. DUCKDB_CMAKE_BUILD_TYPE takes precedence.DUCKDB_EXTENSION_CONFIGS is unsupported. Setting it fails fast.cargo build -vv -F bundled-cmake for CMake configure/build logs.When linking against a DuckDB library already on the system (so not using any of the bundled features), you can set the DUCKDB_LIB_DIR environment variable to point to a directory containing the library. You can also set the DUCKDB_INCLUDE_DIR variable to point to the directory containing duckdb.h.
Linux ARM64 example:
wget https://github.com/duckdb/duckdb/releases/download/v1.5.5/libduckdb-linux-arm64.zip
unzip libduckdb-linux-arm64.zip -d libduckdb
export DUCKDB_LIB_DIR=$PWD/libduckdb
export DUCKDB_INCLUDE_DIR=$DUCKDB_LIB_DIR
export LD_LIBRARY_PATH=$DUCKDB_LIB_DIR
cargo build --examples
For x86-64 Linux, replace arm64 with amd64 in the archive name and URL.
macOS example:
wget https://github.com/duckdb/duckdb/releases/download/v1.5.5/libduckdb-osx-universal.zip
unzip libduckdb-osx-universal.zip -d libduckdb
export DUCKDB_LIB_DIR=$PWD/libduckdb
export DUCKDB_INCLUDE_DIR=$DUCKDB_LIB_DIR
export DYLD_FALLBACK_LIBRARY_PATH=$DUCKDB_LIB_DIR
cargo build --examples
Setting DUCKDB_DOWNLOAD_LIB=1 makes the build script download pre-built DuckDB binaries from GitHub Releases. This links against the dynamic library in the archive and effectively automates the manual steps above. Leave DUCKDB_STATIC unset: setting it requests a static duckdb_static library, which the downloaded archives do not contain. The archives are cached in target/duckdb-download/<target>/<version> and that directory is automatically added to the linker search path. The downloaded version always matches the DuckDB version encoded in the libduckdb-sys crate version.
DUCKDB_DOWNLOAD_LIB=1 cargo test
Installing the DuckDB development packages will usually be all that is required, but
the build helpers for pkg-config
and vcpkg have some additional configuration
options. vcpkg-rs uses static libraries by default. To opt into dynamic
linking, set the VCPKGRS_DYNAMIC=1 environment variable before building.
When none of the options above are used, the build script falls back to this discovery path and will emit the appropriate cargo:rustc-link-lib directives if DuckDB is found on your system.
Cross-compiling with bundled is best-effort (not covered by CI): it compiles DuckDB from C++ source and so needs a working C++ cross-compiler for the target, not just the Rust toolchain. If you can't get one working, prefer one of the non-bundled options above (e.g. DUCKDB_LIB_DIR or DUCKDB_DOWNLOAD_LIB=1) and link a pre-built library for the target instead.
When using the bundled feature, the ICU extension is not included due to crates.io's 10MB package size limit. This means some date/time operations (like now() - interval '1 day' or ts::date casts) will fail. You can load ICU at runtime:
conn.execute_batch("INSTALL icu; LOAD icu;")?;
Alternatively, link against a system libduckdb that was compiled with ICU (see build instructions above).
If you are working from a duckdb-rs checkout, you can also use bundled-cmake,icu to compile ICU in through DuckDB's CMake build.
We use bindgen to generate the Rust
declarations from DuckDB's C header file. bindgen
recommends
running this as part of the build process for libraries that use it. We tried
this briefly (duckdb 0.10.0, specifically), but it had some annoyances:
libduckdb-sys (and therefore duckdb) increased
dramatically.bindgen requires a relatively recent version of Clang, which many
systems do not have installed by default.bindgen also requires the DuckDB header file to be present.So we try to avoid running bindgen at build time by shipping
pregenerated bindings for DuckDB.
If you use the bundled feature, you will get pregenerated bindings for the
bundled version of DuckDB. If you want to run bindgen at build time to
produce your own bindings, use the buildtime_bindgen Cargo feature.
A Connection is Send but not Sync: you may move it between threads, but you cannot share a single connection across threads at the same time. DuckDB does support concurrent access through multiple connections to the same database (see DuckDB's concurrency docs.
For multi-threaded applications, give each thread its own connection. The simplest way is a connection pool via the r2d2 feature:
use duckdb::{DuckdbConnectionManager, params};
let manager = DuckdbConnectionManager::file("file.db")?;
let pool = r2d2::Pool::new(manager)?;
// Each worker checks out its own connection from the pool.
let conn = pool.get()?;
conn.execute("INSERT INTO foo (bar) VALUES (?)", params![1])?;
duckdb-rs covers two distinct use cases, and you normally pick exactly one:
.duckdb_extension that DuckDB loads at runtime. Enable the loadable-extension feature.Do not enable loadable-extension for a regular client application. It replaces DuckDB's normal C API functions with extension wrappers that are only initialized when DuckDB loads your extension. Outside an extension context, calls such as Connection::open_in_memory() then panic with DuckDB API not initialized or DuckDB feature omitted.
The hello-ext example shows the extension API in action: it registers a hello(name) table function via the loadable-extension feature and the duckdb_entrypoint_c_api macro.
cargo build alone does not produce a loadable extension though. DuckDB only loads files ending in .duckdb_extension that carry a metadata footer matching the target DuckDB version. A bare shared library fails with The file is not a DuckDB extension. The metadata at the end of the file is invalid. Appending that footer requires DuckDB's extension build tooling.
So rather than wiring this up by hand, start from the official extension-template-rs template for a complete, ready-to-build Rust extension. It handles the metadata step, platform/version detection, tests, and CI. Its make debug builds the shared library and then appends the footer, producing a .duckdb_extension you load with duckdb -unsigned.
duckdb-rs is built and tested with stable Rust, and keeps a rolling MSRV (minimum supported Rust version) that may only be updated when the encoded DuckDB major/minor version changes, on a need basis (e.g. project dependencies bump their MSRV or a particular Rust feature is useful for us). The new MSRV will be at least 6 months old. DuckDB patch updates and crate patch releases (e.g. 1.10500.0 to 1.10500.1) are guaranteed to keep the same MSRV.
We welcome contributions! Take a look at CONTRIBUTING.md for more information.
Join our Discord to chat with the community in the #rust channel.
Copyright (c) Stichting DuckDB Foundation
Licensed under the MIT license.
(top 30 of 75)
Rust
99.1%