pola-rs/polars · error

data type must be FixedSizeList (got {dtype:?})

Error message

data type must be FixedSizeList (got {dtype:?})

What it means

Compile-time panic from the #[polars_expr] attribute macro in pyo3-polars-derive (lib.rs:275). After parsing the attribute arguments into attr::ExprsFunctionOptions, the macro needs one of three keys — output_type=<DataType>, output_type_func=<fn name>, or output_type_func_with_kwargs=<fn name> — to generate the schema/field function Polars calls to learn the expression's output dtype. If none was supplied (e.g. a bare #[polars_expr]), all options are None and the macro panics with "didn't understand polars_expr attribute". Declaring the output type is mandatory; there is no inference.

Source

Thrown at crates/polars-arrow/src/array/fixed_size_list/mutable.rs:54

        let dtype = FixedSizeListArray::default_datatype(values.dtype().clone(), size);
        Self::new_from(values, dtype, size)
    }

    /// Creates a new [`MutableFixedSizeListArray`] from a [`MutableArray`] and size.
    pub fn new_with_field(values: M, name: PlSmallStr, nullable: bool, size: usize) -> Self {
        let dtype = ArrowDataType::FixedSizeList(
            Box::new(Field::new(name, values.dtype().clone(), nullable)),
            size,
        );
        Self::new_from(values, dtype, size)
    }

    /// Creates a new [`MutableFixedSizeListArray`] from a [`MutableArray`], [`ArrowDataType`] and size.
    pub fn new_from(values: M, dtype: ArrowDataType, size: usize) -> Self {
        assert_eq!(values.len(), 0);
        match dtype {
            ArrowDataType::FixedSizeList(..) => (),
            _ => panic!("data type must be FixedSizeList (got {dtype:?})"),
        };
        Self {
            size,
            length: 0,
            dtype,
            values,
            validity: None,
        }
    }

    #[inline]
    fn has_valid_invariants(&self) -> bool {
        (self.size == 0 && self.values().len() == 0)
            || (self.size > 0 && self.values.len() / self.size == self.length)
    }

    /// Returns the size (number of elements per slot) of this [`FixedSizeListArray`].
    pub const fn size(&self) -> usize {

View on GitHub (pinned to 5d8ebabf11)

Solutions

  1. Add output_type=<DataType> for a fixed dtype, e.g. #[polars_expr(output_type=Int64)] (DataType variant from polars_core)
  2. If the dtype depends on input dtypes, write a field function fn my_out(fields: &[Field]) -> PolarsResult<Field> and use #[polars_expr(output_type_func=my_out)]
  3. If that field function itself needs kwargs, use #[polars_expr(output_type_func_with_kwargs=my_out)] with fn my_out(fields: &[Field], kwargs: MyKwargs) -> PolarsResult<Field>
  4. Copy the exact attribute form from the example crate (example/derive_expression/expression_lib/src/expressions.rs) to avoid keyword typos

Example fix

// before — attribute carries no output declaration
#[polars_expr]
fn double(inputs: &[Series]) -> PolarsResult<Series> {
    todo!()
}

// after — declare the output dtype (or use output_type_func / output_type_func_with_kwargs)
#[polars_expr(output_type=Int64)]
fn double(inputs: &[Series]) -> PolarsResult<Series> {
    todo!()
}
Defensive patterns

Strategy: validation

Validate before calling

# ci/check_polars_expr.py — run before cargo build
import re, sys, pathlib
pat = re.compile(r"#\[polars_expr(\([^)]*\))?\]", re.S)
keys = ("output_type=", "output_type_func=", "output_type_func_with_kwargs=")
for f in pathlib.Path("src").rglob("*.rs"):
    text = f.read_text()
    for m in re.finditer(r"#\[polars_expr[^\]]*\]", text):
        attr = m.group(0)
        if not any(k in attr for k in keys):
            sys.exit(f"{f}: attribute `{attr}` declares no output type; add output_type=..., output_type_func=..., or output_type_func_with_kwargs=...")

Prevention

When it happens

Trigger: Using the attribute with no arguments: #[polars_expr] on fn f(inputs: &[Series]) -> PolarsResult<Series>. Note the failure mode split: an UNKNOWN keyword inside the parens panics earlier in attr.rs:54 with "didn't recognize attribute"; this message fires specifically when the attribute is empty or contains no recognized option.

Common situations: First plugin written from a tutorial that omitted the attribute contents; stripping the attribute while debugging; assuming the return type annotation lets the macro infer the dtype (it does not — dtype is a runtime Polars concept, not the Rust return type); typos in the key name route to the sibling "didn't recognize attribute" panic instead.

Related errors


AI-assisted analysis of pola-rs/polars@5d8ebabf11 (2026-08-19). Data as JSON: /api/errors/2c9e09ded994eef6. Report an issue: GitHub.