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

  1. Ensure the builder's function-producing method is called exactly once before `finish()`.
  2. Chain the calls fluently (`...build(...).finish()`) so the compiler/ordering guarantees the function is set.
  3. 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

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


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)