databendlabs/databend · error
typed builder must produce exactly one function
Error message
typed builder must produce exactly one function
What it means
`InlineFunctionBuilder::finish` panics when the internal `function` field is still `None`, meaning no function was ever stored in the typed builder. The builder API is designed so exactly one function is produced before `finish` is called; `expect` enforces this invariant loudly. This is a builder lifecycle misuse rather than a runtime data problem.
Solutions
- Ensure the builder's function-producing method is called exactly once before `finish()`.
- Chain the calls fluently (`...build(...).finish()`) so the compiler/ordering guarantees the function is set.
- If building conditionally, make sure every branch sets the function before finishing.
Example fix
// before
let builder = InlineFunctionBuilder::new("my_func");
let f = builder.finish(); // panics: function never set
// after
let f = InlineFunctionBuilder::new("my_func").build(params, body).finish(); Defensive patterns
Strategy: type-guard
Validate before calling
// before finish(): debug_assert!(builder.function.is_some(), "finish() called before function was built");
Type guard
fn ready_to_finish(b: &InlineFunctionBuilder) -> bool { b.function.is_some() } Try / catch
// prefer guarding; panics from expect are unrecoverable in Rust:
if !ready_to_finish(&builder) { return Err(ErrorCode::Internal("function not built")); } Prevention
- Use fluent chaining so build-then-finish ordering is enforced by construction.
- Never store builders across conditional branches that may skip the build step.
- Code-review new function registrations for complete builder chains.
When it happens
Trigger: Calling `.finish()` on an `InlineFunctionBuilder` without having called the builder's function-producing step (the method that sets `self.function = Some(...)`), or calling `finish()` twice after the builder was consumed/reset.
Common situations: Writing a new scalar function registration and forgetting the intermediate build step; copy-pasting a builder chain and dropping the line that constructs the function; conditional code paths that skip the build call.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- function factory must be set before registering
- typed calc_domain must be specified
- Unsupported format for
- Unsupported source type. Expected path, pandas.DataFrame…
- Access denied: is outside allowed directories
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/99ca5b8360466012.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/expression/src/function/function_builder.rs:288
&self.name
}
fn collect(&mut self, function: Function) {
self.functions.push(function);
}
}
impl InlineFunctionBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
function: None,
}
}
pub fn finish(self) -> Function {
self.function
.expect("typed builder must produce exactly one function")
}
}
impl ScalarFunctionCollect for InlineFunctionBuilder {
const FOR_FACTORY: bool = true;
fn name(&self) -> &str {
&self.name
}
fn collect(&mut self, function: Function) {
assert!(self.function.is_none(), "function already built");
self.function = Some(function);
}
}
pub struct ScalarFunctionArityBuilder<B> {
builder: B,View on GitHub (pinned to 288d84d76e)