# duckdb-rs wraps DuckDB in a rusqlite shaped Rust API

> duckdb-rs is the official Rust binding for DuckDB, offering typed queries, native Arrow, Parquet, JSON and CSV handling, custom table functions written in Rust, and a second experimental crate aimed at DuckDB's new v2 C API. The hard part of using it is not the API but the build, because feature flags decide whether you compile DuckDB from source, link a prebuilt binary, or use a system library.

**duckdb/duckdb-rs** —  Ergonomic bindings to duckdb for Rust 

- Repository: https://github.com/duckdb/duckdb-rs
- Stars: 981 · Forks: 256
- Language: Rust
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/duckdb-duckdb-rs

## Type-safe queries with a familiar shape

The binding is described as ergonomic, and the API is explicitly inspired by rusqlite, which is the fastest way to understand what you get. A connection is opened, statements are executed with parameters, and rows are mapped into Rust structs by index. The three capabilities the project lists are type-safe bindings with that rusqlite flavour, native reading and writing of Arrow, Parquet, JSON and CSV, and the ability to create DuckDB extensions in Rust with custom scalar and table functions. That last one is the unusual capability. It means the extension surface is not restricted to C, so a team that wants a custom aggregate or a table function over its own domain type can write it in Rust rather than dropping to the C API and its bindgen cycle. The type-safe part is worth being concrete about, because it is the reason to prefer this over shelling out to the duckdb CLI or embedding the engine through raw FFI yourself.

## Getting started takes three cargo commands

The quickstart is a fresh project with one dependency added. The crate name on crates.io is duckdb, and the bundled feature is what makes the build self-contained:

```bash
cargo new quack-in-rust
cd quack-in-rust
cargo add duckdb -F bundled
```

The program itself creates an in-memory connection, makes a table, inserts rows both as a batch of literal SQL and as a parameterised statement, and then maps the result rows into a struct. The table creation and its parameters look like this:

```rust
    conn.execute(
        "CREATE TABLE ducks (id INTEGER PRIMARY KEY, name TEXT)",
        [], // empty list of parameters
    )?;
```

and the query side uses prepare, query_map and a collect into a Vec:

```rust
    let ducks = conn
        .prepare("FROM ducks")?
        .query_map([], |row| {
            Ok(Duck {
                id: row.get(0)?,
                name: row.get(1)?,
            })
        })?
        .collect::<Result<Vec<_>>>()?;
```

The error type is a crate-level Result alias, so the ? operator works throughout and a failure in the database becomes a failure in your function. Running the program with cargo run prints the rows.

## duckdb-neo targets the new v2 C API

The repository now contains a second wrapper crate, duckdb-neo, and the distinction is about which C API is bound. The existing duckdb crate targets the v1 API, while duckdb-neo targets DuckDB's newer v2 API exposed in duckdb_v2.h, and the README states plainly that the v2 C API is in rapid development and that duckdb-neo should be considered unstable. Both crates sit on the same libduckdb-sys crate, so they share the same bundled DuckDB, the same build options and the same version number. The v2 shape is different in practice, since a query returns a result you pull chunks from rather than a row iterator:

```rust
use duckdb_neo::{
    Parameters,
    environment::{Environment, StorageLocation},
};

fn main() -> duckdb_neo::Result<()> {
    let environment = Environment::new()?;
    let database = environment.open(StorageLocation::InMemory)?;
    let connection = database.connect()?;
```

The binding layer exposes the two API generations behind features. libduckdb-sys ships the v1 bindings as libduckdb_sys::v1 under the default capi-v1 feature, and the v2 bindings as libduckdb_sys::v2 behind the capi-v2 feature, with duckdb-neo disabling the default features so only the v2 bindings are generated. One detail is a trap: duckdb-neo carries its own capi-v2-p2 feature for API surface the bundled DuckDB does not provide yet, and enabling it does not compile.

## Feature flags decide the build and the payload

The feature list is where the real decisions live, and the flags fall into three groups. The first group adds DuckDB extensions statically, but only through the CMake build backend: autocomplete, httpfs for the HTTP and S3 filesystem extension on Linux and macOS, icu for locale aware operations, tpch and tpcds for the benchmark extensions. The second group is the format and virtual table surface. vtab is the base for custom table functions, vtab-arrow adds Arrow integration for virtual tables, vscalar adds custom scalar functions and vscalar-arrow the vectorized variant. vtab-excel is listed as a deprecated no-op kept for feature compatibility, which is the kind of detail that saves you an afternoon of wondering why enabling it does nothing. The third group is the data integration features, including appender-arrow for efficient bulk insertion of Arrow data, polars for working with Polars DataFrames, and rust_decimal, which restores ToSql and FromSql conversions for the rust_decimal crate when values fit. The json and parquet features enable reading and writing those files and both imply bundled, which is a detail worth noticing, because enabling a format feature silently changes how the engine is built.

## The examples cover the ground a user actually needs

The examples directory is a catalogue of use cases rather than a set of toy snippets, and each one is tied to a feature so you can see the flags in context. The basic example covers creating tables, inserting data and querying with and without Arrow. The appender example shows bulk insertion through the appender API with transactions, which is the path you want when loading data rather than inserting it row by row. The parquet example reads Parquet files through DuckDB's Parquet extension. The vtab example defines a custom table function returning primitive, LIST and STRUCT columns, and vscalar defines a custom scalar function. The arrow_vtab example queries an in-memory Arrow RecordBatch from DuckDB SQL, which is the interesting direction, asking the engine to query data it never leaves memory for. There is also a repl example that is an interactive SQL REPL, and hello-ext, which is a DuckDB extension written in Rust. Running them is a single cargo invocation:

```bash
cargo run --example basic
```

and where a feature is required you pass it explicitly:

```bash
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
```

The extension example is a library target rather than an executable, so it is built instead:

```bash
cargo build --example hello-ext --features loadable-extension
```

## Bundled, prebuilt or system library

There are three ways to get the DuckDB engine into your binary, and the choice is the one thing to make deliberately. Without the bundled feature, the build links against a pinned prebuilt DuckDB that it downloads, or against a system library if that is what the environment provides. With bundled, DuckDB is compiled from source as part of your build, which gives a self-contained binary and a longer build. The bundled option is what the format features imply, and the examples show it combining with others:

```bash
cargo run --example basic --features bundled
cargo run --example arrow_vtab --features "bundled vtab-arrow"
```

The workspace layout explains the rest. The root Cargo.toml declares four members, crates/duckdb, crates/duckdb-neo, crates/libduckdb-sys and crates/duckdb-loadable-macros, which is the layering of the safe wrapper, the experimental wrapper, the raw FFI bindings and the procedural macros that make the extension API ergonomic. The shared dependency set is broad for a database binding, with arrow at version 60, chrono, r2d2 for connection pooling, polars at 0.49.1, uuid, serde and serde_json among others, and bindgen at 0.72.1 alongside cc and pkg-config, which is what tells you the FFI layer generates bindings at build time. The workspace targets the 2024 edition with a minimum Rust version of 1.88.

## Version numbering and release cadence

The version scheme here needs a word of explanation, because the numbers do not look like a normal crate release. The workspace package version is 1.20000.0, and the workspace crates are pinned with exact version requirements using the = operator, so the crates move together and the DuckDB library they build against moves with them. The published releases follow a different-looking scheme, with v1.10506.1 dated 2026-10-16, v1.10506.0 dated 2026-09-30 and v1.10505.0 dated 2026-07-22, where the long number tracks the DuckDB version the bindings are generated against. The practical advice is to read the number as a DuckDB version rather than a semantic version, because what you actually care about when you upgrade is which DuckDB you get. The repository is not archived, the last push was on 2026-10-20, and the project ships an upgrade.sh script at the top level alongside a rust-toolchain.toml and a Cargo.lock, so the toolchain is pinned too. The licence is MIT, and the top level tree also carries scripts/ and a.cargo/ directory for cargo configuration.

## Conclusion

duckdb-rs fits a Rust application that wants analytical SQL over columnar files without a server, since the crate gives typed queries, Arrow interchange and the appender API for bulk loads while keeping the whole engine in process. It does not fit a team that wants a light dependency tree, because the default build links the DuckDB library and the bundled feature compiles it from source, and it does not fit anyone who needs the new v2 C API to be stable, since the README asks you to treat duckdb-neo as unstable while that API is in rapid development. Before you start, pick your build mode deliberately, run cargo add duckdb -F bundled if you want a self-contained binary, and check the crate version you pull against the DuckDB version it pins, since the workspace manifest reads 1.20000.0 while the published releases are numbered v1.10506.1 and the last push was on 2026-10-20.

## FAQ

### How do I add DuckDB to a Rust project?

Create the project, then run cargo add duckdb -F bundled to add the crate with the bundled feature, which compiles DuckDB from source so the binary is self-contained. Without that feature the build links a pinned prebuilt DuckDB or a system library.

### What is the difference between duckdb and duckdb-neo?

They are two wrapper crates in the same repository. The duckdb crate binds the v1 C API, while duckdb-neo targets the new v2 C API in duckdb_v2.h and is described as unstable while that API is in rapid development.

### Can I write a custom table function in Rust?

Yes. The vtab feature provides the base support for custom table functions and virtual tables, vtab-arrow adds Arrow integration, and vscalar covers custom scalar functions. The examples named vtab, vscalar and arrow_vtab show the usage.

### Which data formats are supported?

Arrow, Parquet, JSON and CSV can be read and written natively. The json and parquet Cargo features enable those formats and both imply the bundled feature, so they also change how the engine is built.

### How often is duckdb-rs released?

The listed releases include v1.10506.1 on 2026-10-16, v1.10506.0 on 2026-09-30 and v1.10505.0 on 2026-07-22, and the last push was on 2026-10-20.

## Sources

- [duckdb/duckdb-rs on GitHub](https://github.com/duckdb/duckdb-rs)
- [Issues](https://github.com/duckdb/duckdb-rs/issues)
- [License: MIT](https://github.com/duckdb/duckdb-rs/blob/main/LICENSE)
- [README](https://github.com/duckdb/duckdb-rs/blob/main/README.md)
- [Releases](https://github.com/duckdb/duckdb-rs/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/duckdb-duckdb-rs
