Scalar user-defined functions (UDFs) compute one value per input row and can appear anywhere in aDocumentation 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.
SELECT list or WHERE clause — SELECT my_func(x) FROM t. With better-duck’s udf feature, the #[duckdb_scalar] attribute macro turns an ordinary Rust function into a DuckDB scalar function with zero unsafe code and no manual vector handling. Parameter and return types are inferred directly from the Rust signature via the DuckLogicalType trait, so any type that already round-trips through AppendAble (all integer widths, floats, String/&str, bool, and more) works without any extra type table.
Enabling UDF Support
Scalar functions are gated behind theudf feature flag. Add it to your Cargo.toml:
Basic Scalar Function
Annotate anyfn with #[duckdb_scalar], then call <fn_name>::register(&mut conn) before querying. The macro generates a private module named after your function that exposes the register function.
name option:
The
#[duckdb_scalar] macro does not consume the original function — it remains a perfectly
ordinary, directly callable Rust function. The generated <fn_name> module containing register
is placed alongside it in the same scope.NULL Handling
UseOption<T> for parameters that may receive NULL and for return types that may produce NULL. When any non-Option parameter receives a NULL value, DuckDB’s default NULL-propagation kicks in automatically — the function body is not called and the output row is set to NULL.
- SQL
- Rust registration
Option<T>, and omitted otherwise.
Error Handling
ReturnResult<T, E> to propagate errors back to DuckDB as a query failure. On Err, the query fails and the error message comes from E’s Display implementation. The connection remains fully usable afterwards.
duck_bail! macro for early returns with a formatted message from inside any fallible UDF body:
duck_bail! expands to return Err(format!(...).into()). The surrounding function’s error type must implement From<String>, which is true for String itself and for Box<dyn std::error::Error + Send + Sync>.
Shared State with duck_state!
Use #[duckdb_scalar(state(Type, init_expr))] to attach a value to the function at registration time. The value is initialized once with init_expr, stored as VScalar::State, and is accessible from inside the function body via the duck_state!(Type) macro. The state is Cloned each time duck_state! is called.
state option accepts any Type and any Rust expression for init_expr — it is evaluated at the call to register. The state value is shared read-only across all invocations on all rows; it is not mutable during query execution.
volatile Flag
By default, DuckDB constant-folds zero-argument scalar functions — a call like SELECT my_const() is evaluated once at plan time and the result is inlined. Use volatile to opt out:
volatile functions are re-evaluated for every row, even with zero parameters. Use this when the function has side effects or depends on external state that may change between calls.
Panic Handling
If the function body panics and the crate is built withpanic = "unwind" (the default for dev and test profiles), the panic is caught and reported to DuckDB as a query error. The connection remains usable.
Complete Attribute Reference
| Option | Description |
|---|---|
name = "sql_name" | Override the SQL function name (defaults to the Rust function name) |
crate = ::path | Re-export escape hatch for the generated code’s ::better_duck_core path |
volatile | Disable DuckDB’s zero-argument constant-folding |
state(Type, init_expr) | Attach a VScalar::State value, readable inside the body via duck_state!(Type) |