{"record":{"id":"8e9c0b672308657d","repo":"pola-rs/polars","slug":"could-not-unwrap-required-zipvalidity-iterato","errorCode":null,"errorMessage":"Could not 'unwrap_required'. 'ZipValidity' iterator has nulls.","messagePattern":"Could not 'unwrap_required'\\. 'ZipValidity' iterator has nulls\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/polars-arrow/src/bitmap/utils/zip_validity.rs","lineNumber":206,"sourceCode":"}\n\nunsafe impl<T, I, V> TrustedLen for ZipValidity<T, I, V>\nwhere\n    I: TrustedLen<Item = T>,\n    V: TrustedLen<Item = bool>,\n{\n}\n\nimpl<T, I, V> ZipValidity<T, I, V>\nwhere\n    I: Iterator<Item = T>,\n    V: Iterator<Item = bool>,\n{\n    /// Unwrap into an iterator that has no null values.\n    pub fn unwrap_required(self) -> I {\n        match self {\n            ZipValidity::Required(i) => i,\n            _ => panic!(\"Could not 'unwrap_required'. 'ZipValidity' iterator has nulls.\"),\n        }\n    }\n\n    /// Unwrap into an iterator that has null values.\n    pub fn unwrap_optional(self) -> ZipValidityIter<T, I, V> {\n        match self {\n            ZipValidity::Optional(i) => i,\n            _ => panic!(\"Could not 'unwrap_optional'. 'ZipValidity' iterator has no nulls.\"),\n        }\n    }\n}\n","sourceCodeStart":188,"sourceCodeEnd":218,"githubUrl":"https://github.com/pola-rs/polars/blob/9b5d73fd00236295624374b075d16b1fe6ec6df9/crates/polars-arrow/src/bitmap/utils/zip_validity.rs#L188-L218","documentation":"ZipValidity is an enum: Required(iter) wraps iteration over an array with no validity; Optional(iter) yields Option<T> and can produce nulls. unwrap_required() is the fast path that asserts the Required variant; calling it on an Optional value panics because nulls cannot be represented in the unwrapped iterator.","triggerScenarios":"arr_iter.unwrap_required() on the ZipValidity produced for an array whose validity().is_some(); the classic caller is a dense fast path that skipped its validity().is_none() guard.","commonSituations":"Performance-tuned loops assuming a column is fully dense; after a filter, left join, or strided slice introduces the first nulls into a previously dense column; data-dependent panics that only fire on some inputs.","solutions":["Gate the fast path: if arr.validity().is_none() { ...unwrap_required()... } else { fall back to Option handling }","Handle nulls explicitly: keep the Optional iterator and map/flatten the Option items","Centralize the guard in a helper so every call site of unwrap_required is protected"],"exampleFix":"// before\nlet vals: Vec<&str> = iter.unwrap_required().collect();\n\n// after\nlet vals: Vec<&str> = if arr.validity().is_none() {\n    iter.unwrap_required().collect()\n} else {\n    iter.flatten().collect()\n};","handlingStrategy":"validation","validationCode":"fn is_dense(arr: &dyn polars_arrow::array::Array) -> bool {\n    arr.validity().is_none()\n}\n// only take the unwrap_required fast path when is_dense(&arr)","typeGuard":"fn can_unwrap_required<T, I, V>(zv: &polars_arrow::bitmap::utils::ZipValidity<T, I, V>) -> bool {\n    matches!(zv, polars_arrow::bitmap::utils::ZipValidity::Required(_))\n}","tryCatchPattern":null,"preventionTips":["Guard every unwrap_required with validity().is_none()","Prefer handling Option items generically over assuming density","Add null-containing fixtures to tests for dense fast paths"],"tags":["rust","polars","arrow","iterator","nulls"],"backgroundTag":"null-unwrapping","analyzedSha":"9b5d73fd00236295624374b075d16b1fe6ec6df9","analyzedAt":"2026-08-19T12:15:06.350Z","contentChangedAt":"2026-08-19T12:15:06.350Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}