pola-rs/polars · error

expr expected, did you call when().then().otherwise?

Error message

expr expected, did you call when().then().otherwise?

What it means

ChainedThen::otherwise() rebuilds a when/then/otherwise chain by iterating conditions and statements in reverse and nesting ternary expressions. The invariant is one then() per when(), so statements_iter.next() always yields; if a ChainedThen was built with more conditions than statements (only possible by constructing the structs manually or via corrupted deserialization), next() returns None and this expect panics.

Source

Thrown at crates/polars-plan/src/dsl/arity.rs:129

        //             )                                         |       |
        //         )                                            _|      _|
        //
        // by iterating LIFO we first create
        // `inner` and then assign that to `otherwise`,
        // which will be used in the next layer `outer`
        //

        let conditions_iter = self.conditions.into_iter().rev();
        let mut statements_iter = self.statements.into_iter().rev();

        let mut otherwise = expr.into();

        for e in conditions_iter {
            otherwise = ternary_expr(
                e,
                statements_iter
                    .next()
                    .expect("expr expected, did you call when().then().otherwise?"),
                otherwise,
            );
        }

        otherwise
    }
}

/// Start a `when-then-otherwise` expression.
pub fn when<E: Into<Expr>>(condition: E) -> When {
    When {
        condition: condition.into(),
    }
}

pub fn ternary_expr(predicate: Expr, truthy: Expr, falsy: Expr) -> Expr {
    Expr::Ternary {
        predicate: Arc::new(predicate),

View on GitHub (pinned to df599052da)

Solutions

  1. Use the fluent API: every when(cond) must be followed by then(expr) before otherwise(...)
  2. Do not construct ChainedWhen/ChainedThen literally; keep conditions.len() == statements.len() if you must
  3. Serialize and deserialize expressions with the same polars version on both ends

Example fix

// before (manual construction breaks the invariant)
let e = ChainedThen { conditions: vec![c1, c2], statements: vec![s1] }.otherwise(def);

// after: fluent API keeps pairing correct
let e = when(c1).then(s1).when(c2).then(s2).otherwise(def);
Defensive patterns

Strategy: validation

Validate before calling

// before calling otherwise(), assert the builder invariant
assert_eq!(chained.conditions.len(), chained.statements.len(), "each when() needs a then()");

Prevention

When it happens

Trigger: Constructing ChainedWhen/ChainedThen structs directly with mismatched conditions/statements vectors, or deserializing a hand-crafted/older-format expression where the pairing is broken. The fluent API (when().then().when().then().otherwise()) cannot produce it.

Common situations: Code that builds DSL structs manually instead of the fluent API; expression round-trips between polars versions with incompatible internal serialization.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/6cf51e7a7b87d0b6. Report an issue: GitHub.