Skip to main content
Requires feature = "udf". See the UDF overview for shared semantics.
#[duckdb_scalar] turns a Rust function into a DuckDB scalar function — one value computed per row, usable in a SELECT list or WHERE clause.
Parameter and return types are inferred from the signature. A function must return a value (a bare fn f() is rejected). An Option<T> return/parameter maps to NULL; a Result<T, E> return fails the query on Err.

NULL handling

  • A non-Option parameter short-circuits NULL rows: DuckDB fills the output with NULL without calling your body (so the slot’s value may not even be valid to read).
  • Any Option<T> parameter auto-enables special_handling, so your body is called for NULL inputs and decides what to do.

Options

name must be a non-empty string literal without NUL bytes; specifying name or state twice is a compile error. The function also remains callable as a plain Rust fn.

Shared state with duck_state!

With state(Type, init_expr), read a Clone copy of the shared state inside the body with duck_state!(Type):
The same state is shared across every row of a scan (and every worker thread), so any interior mutation must be synchronized. duck_state! panics if called outside a state-carrying scalar body — contained by the callback machinery, so it surfaces as a normal query error. See helper macros.

The VScalar trait

The macro implements better_duck_core::udf::VScalar. Implement it by hand when you need multiple overloads or bind-time constant folding:
A ScalarSignature is exact(params, ret) or variadic(param, ret) — each becomes a DuckDB overload. Register with conn.register_scalar_function::<T>(name) or register_scalar_function_with_state::<T>(name, state). ScalarBindInfo exposes the call’s argument Expressions (argument_count, argument), the bind-time client_context() for folding a constant argument, and set_bind_data. The execution handle reads State / per-query BindData back. Work over DataChunkHandle / VectorMut.

See also