Documentation Index
Fetch the complete documentation index at: https://mintlify.com/nimdeveloper/better-duck/llms.txt
Use this file to discover all available pages before exploring further.
DuckValue is the central Rust type in better-duck-core that represents any value that can be stored in a DuckDB column. It appears as the return type of DuckRow::get, is used to supply parameters to Connection::execute_with for parameterized queries, and is passed directly to Appender::append via the AppendAble trait. Every column in every row you read from DuckDB becomes a DuckValue variant, and every value you bind into a prepared statement is expressed as one.
The DuckValue Enum
DuckValue is a #[non_exhaustive] enum defined in better_duck_core::types::value. Each variant carries the most natural Rust type for the corresponding DuckDB SQL type. The full set of variants, grouped by category, is shown below.
DuckValue is marked #[non_exhaustive]. New variants will be added as
DuckDB gains new types. Always include a _ catch-all arm in every match
so your code keeps compiling after upgrades.
Primitive numeric variants
| Variant | Inner Rust type | DuckDB type |
|---|
Boolean(bool) | bool | BOOLEAN |
TinyInt(i8) | i8 | TINYINT |
UTinyInt(u8) | u8 | UTINYINT |
SmallInt(i16) | i16 | SMALLINT |
USmallInt(u16) | u16 | USMALLINT |
Int(i32) | i32 | INTEGER |
UInt(u32) | u32 | UINTEGER |
BigInt(i64) | i64 | BIGINT |
UBigInt(u64) | u64 | UBIGINT |
HugeInt(i128) | i128 | HUGEINT |
UHugeInt(u128) | u128 | UHUGEINT |
Float(f32) | f32 | FLOAT |
Double(f64) | f64 | DOUBLE |
Decimal(rust_decimal::Decimal) | rust_decimal::Decimal | DECIMAL (feature: decimal) |
Text and blob variants
| Variant | Inner Rust type | DuckDB type |
|---|
Text(String) | String | VARCHAR / TEXT |
Blob(Blob) | better_duck_core::types::blob::Blob | BLOB |
Date and time variants
The inner type for each temporal variant depends on whether the chrono feature is enabled. With chrono (the default), you get chrono types. Without it, you get the native types from better_duck_core::types::date_native.
| Variant | chrono inner type | no-chrono inner type | DuckDB type |
|---|
Date(…) | chrono::NaiveDate | DuckDate | DATE |
Time(…) | chrono::NaiveTime | DuckTime | TIME |
Timestamp(…) | chrono::NaiveDateTime | std::time::SystemTime | TIMESTAMP |
TimestampTz(…) | chrono::DateTime<Utc> | std::time::SystemTime | TIMESTAMPTZ |
TimeTz(…) | date_chrono::TimeTz | DuckTimeTz | TIME WITH TIME ZONE |
Interval(…) | chrono::Duration | std::time::Duration | INTERVAL |
TimeNs(…) | chrono::NaiveTime | DuckTimeNs | TIME_NS |
There are also sub-precision timestamp variants (TimestampS, TimestampMs, TimestampNs) that all carry chrono::NaiveDateTime (or std::time::SystemTime without chrono) for second-, millisecond-, and nanosecond-precision timestamp columns respectively.
Composite variants
| Variant | Inner Rust type | DuckDB type |
|---|
List(Vec<DuckValue>) | Vec<DuckValue> | LIST |
Array(Box<[DuckValue]>) | Box<[DuckValue]> | ARRAY (fixed-length) |
Struct(HashMap<String, DuckValue>) | HashMap<String, DuckValue> | STRUCT |
Map(HashMap<DuckValue, DuckValue>) | HashMap<DuckValue, DuckValue> | MAP |
Union(Box<DuckValue>) | Box<DuckValue> (active member) | UNION |
Special variants
| Variant | Inner Rust type | DuckDB type |
|---|
Enum(String) | String | ENUM |
Uuid(DuckUuid) | better_duck_core::types::uuid::DuckUuid | UUID |
Bit(DuckBit) | better_duck_core::types::bit::DuckBit | BIT |
Bignum(DuckBignum) | better_duck_core::types::bignum::DuckBignum | BIGNUM |
Null | — | NULL |
Pattern Matching
Because DuckValue is an enum, the idiomatic way to inspect a column value is with a match expression. The #[non_exhaustive] attribute means the compiler will require a wildcard arm — this is intentional so your code stays forward-compatible as new variants are added.
use better_duck_core::types::value::DuckValue;
match value {
DuckValue::Int(n) => println!("integer: {n}"),
DuckValue::Text(s) => println!("text: {s}"),
DuckValue::Double(f) => println!("float: {f}"),
DuckValue::Null => println!("null"),
_ => println!("other: {value:?}"),
}
The From<T> trait is implemented for all primitive Rust types, so you can convert native values into DuckValue directly and use them with execute_with:
use better_duck_core::types::value::DuckValue;
// Construct explicitly
let mut threshold = DuckValue::Double(8.0);
// Or via From<f64>
let mut threshold: DuckValue = 8.0f64.into();
let mut rows = conn.execute_with(
"SELECT id, label FROM events WHERE score > $1",
&mut [&mut threshold],
)?;
DuckValue also provides two convenience constructors and map-access helpers:
DuckValue::text(s) — creates a Text variant from any Into<String>
value.get(key) — looks up a key in a Map variant, returning Option<&DuckValue>
value.get_mut(key) — mutable lookup into a Map variant
value.contains_key(key) — key-existence check for Map variants
DuckValue as AppendAble
DuckValue implements the AppendAble trait, which means it can be passed directly to both Connection::execute_with (as a prepared-statement bind) and Appender::append (as a bulk-insert row column). Under the hood, DuckValue’s AppendAble impl delegates to DuckValueRef to avoid duplicating per-variant logic.
use better_duck_core::{connection::Connection, types::value::DuckValue};
let mut conn = Connection::open_in_memory()?;
conn.execute_batch("CREATE TABLE logs (level TEXT, code INTEGER);")?;
let mut app = conn.appender("logs", "main")?;
// Append a row with two DuckValue columns
app.append(&mut DuckValue::text("warn"))?;
app.append(&mut DuckValue::Int(404))?;
app.save()?;
See the AppendAble trait documentation for the full contract and instructions on implementing it for custom types.
Composite Types
Constructing composite DuckValue variants requires assembling the inner collection first.
use better_duck_core::types::value::DuckValue;
let list = DuckValue::List(vec![
DuckValue::Int(1),
DuckValue::Int(2),
DuckValue::Int(3),
]);
use better_duck_core::types::value::DuckValue;
let array = DuckValue::Array(
vec![DuckValue::text("a"), DuckValue::text("b")]
.into_boxed_slice(),
);
use better_duck_core::types::value::DuckValue;
use std::collections::HashMap;
let mut fields = HashMap::new();
fields.insert("name".to_string(), DuckValue::text("Alice"));
fields.insert("age".to_string(), DuckValue::Int(30));
let row = DuckValue::Struct(fields);
use better_duck_core::types::value::DuckValue;
use std::collections::HashMap;
let mut entries = HashMap::new();
entries.insert(DuckValue::text("key1"), DuckValue::Int(100));
entries.insert(DuckValue::text("key2"), DuckValue::Int(200));
let map = DuckValue::Map(entries);
When you need a statically-typed list, array, or map and are okay with all
elements sharing the same Rust type, prefer using Vec<T>, Box<[T]>, or
HashMap<K, V> directly with AppendAble rather than wrapping items in
DuckValue. Those generic impls call T::duck_logical_type() at compile time
to determine the DuckDB column type, so they work correctly even with empty
collections — unlike DuckValue::List / Array / Map, whose element type
can only be inferred from the first item (and which fail on empty collections).
Equality and Hashing
DuckValue implements PartialEq, Eq, and Hash, which is what allows DuckValue to be used as a HashMap key (required for the Map variant). A few details worth knowing:
- Floats —
Float and Double variants are compared and hashed via canonical forms where NaN == NaN and -0.0 == +0.0.
- Maps and Structs — hashed order-independently, so iteration order of the inner
HashMap does not affect the hash value.
- SystemTime (no-
chrono timestamps) — hashed via duration_since(UNIX_EPOCH) for cross-platform stability.