Skip to main content
better-duck gives you several ways to reach DuckDB. This page helps you pick the right crate and the right API surface for your task, and which feature flags to turn on.

Which crate?

better-duck-core

The driver. Row-oriented access, prepared statements, the bulk appender, columnar / zero-copy reads, UDFs, async, pooling, and Arrow / Polars interop.

better-duck-diesel

The Diesel 2.3 ORM backend. A typed query builder (DSL), migrations, an r2d2 pool, and a large library of DuckDB-specific SQL functions, aggregates, and operators.

better-duck-tauri

A Tauri v2 plugin that exposes embedded DuckDB to a webview as a local-analytics engine, with JS/TS bindings (core or diesel backend).

better-duck-macros

The UDF and derive macros. Never a direct dependency — reached through core’s udf / derive features.
Use this quick router:

Which API for which task?

Even within the core crate, picking the right tool matters:
  • One value per row in a SELECT / WHERE → a scalar UDF (#[duckdb_scalar]).
  • Rows from a FROM → a table UDF (#[duckdb_table_function]).
  • Bulk insert → the Appender, not row-by-row INSERT.
  • Read a big result fast / zero-copy columns → the columnar feature’s fetch_chunk + as_slice.
  • ORM / typed query builder → better-duck-diesel.
  • Desktop app (webview) database → better-duck-tauri.
  • Map a Rust struct to a row → #[derive(FromRow)] / #[derive(ToRow)].

Core directly, or the Diesel backend?

  • You want the leanest dependency tree and direct, row-oriented control.
  • You need the bulk Appender, columnar / zero-copy reads, or Arrow / Polars export.
  • You’re registering user-defined functions, or driving the engine from inside a Tauri plugin or an embedded context.
  • You prefer writing SQL yourself over a query-builder DSL.
  • You want a typed query builder, compile-time-checked schema (table!), and migrations.
  • You want DuckDB-specific SQL exposed as Diesel DSL (.ilike(), string_agg, date_trunc, ST_*, …) that stock Diesel doesn’t ship.
  • You want r2d2 pooling with a SharedDuckDbConnectionManager and a per-connection on_connect hook (the place to register UDFs or LOAD extensions on every pooled connection).
  • It builds on better-duck-core, so you can still drop to the core connection for raw SQL, the appender, or UDF registration when you need to.
  • You’re building a Tauri v2 desktop app and want DuckDB available in the webview as a local analytics engine, with ready-made JS/TS bindings — backed by either the core or the diesel backend.

Feature-flag decision tree

Start from the defaults (chrono, decimal) and add what your task needs. See Feature flags for the full table.
1

Dates & numbers

Keep chrono (default) for chrono temporals, or disable defaults for the native date/time structs. Keep decimal (default) for rust_decimal. Add time to bind time-crate values, or uuid for uuid::Uuid interop.
2

Extensions

Need JSON, Parquet, or ICU with no runtime install? Bundle them with json / parquet / icu. (Spatial loads at runtime via the extension API, no feature needed in core.)
3

Data interop

Exporting to Arrow? arrow. Want Polars DataFrames? polars (implies arrow). Need fast / zero-copy columnar reads? columnar.
4

Functions & mapping

Registering Rust functions as DuckDB functions? udf (also enables the columnar APIs). Mapping structs/enums, or want transaction! / params!? derive.
5

Concurrency

Async application? async. Connection pooling? pool. Both together give you AsyncPool.

Feature flags

Every core feature explained.

Architecture

The crate family and the FFI safety model.

Quickstart

A minimal end-to-end example with the core crate.

DuckDB essentials

The dialect, types, and extensions through the driver.