{"record":{"id":"163072ee81af92b8","repo":"pola-rs/polars","slug":"subtraction-overflow-self-other","errorCode":null,"errorMessage":"subtraction overflow: {self:?} - {other:?}","messagePattern":"subtraction overflow: (.+?) - (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/polars-utils/src/row_counter.rs","lineNumber":106,"sourceCode":"                // `num_rows()` will start to decrease).\n                physical_rows?\n            };\n\n            Some(Self {\n                physical_rows,\n                deleted_rows,\n            })\n        })()\n        .unwrap_or_else(|| panic!(\"addition overflow: {self:?} + {other:?}\"))\n    }\n\n    /// # Panics\n    /// Panics if subtraction overflows.\n    #[allow(clippy::should_implement_trait)]\n    pub fn sub(self, other: Self) -> Self {\n        let func = |a: usize, b: usize| {\n            a.checked_sub(b)\n                .unwrap_or_else(|| panic!(\"subtraction overflow: {self:?} - {other:?}\"))\n        };\n\n        Self {\n            physical_rows: func(self.physical_rows, other.physical_rows),\n            deleted_rows: func(self.deleted_rows, other.deleted_rows),\n        }\n    }\n\n    /// Returns the number of rows after applying deletions. This returns an\n    /// error if there are more deleted rows than physical rows.\n    pub fn num_rows(&self) -> PolarsResult<usize> {\n        self.physical_rows\n            .checked_sub(self.deleted_rows)\n            .ok_or_else(|| {\n                polars_err!(\n                    ComputeError: \"RowCounter: Invalid state: \\\n                    number of rows removed by deletion files ({}) \\\n                    is greater than the number of rows physically present ({})\",","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/pola-rs/polars/blob/fe841f959ef4d2ceefc05a310d33ed7b1ab24e5e/crates/polars-utils/src/row_counter.rs#L88-L124","documentation":"RowCounter::sub subtracts another RowCounter (physical_rows and deleted_rows counters) field-by-field and panics if any field would underflow below zero. The library treats a negative row count as an unrecoverable logic error, so it panics instead of returning a wrapped/checked value.","triggerScenarios":"Calling sub() with an `other` RowCounter whose physical_rows or deleted_rows exceeds the receiver's, e.g. resolve_negative_slice subtracting a larger negative-slice row count from a smaller counter.","commonSituations":"Slice arithmetic bugs where a negative slice's start exceeds the row count; mis-estimated chunk boundaries in parallel engines; using a RowCounter accumulated over a different range than the one being subtracted.","solutions":["Verify the subtrahend RowCounter is derived from the same (or a smaller) row range as the minuend","Add checked_sub / debug assertions upstream to catch the inconsistent slice before sub() is reached","Print both RowCounters (they implement Debug) to see which field overflows and fix the producing code","Upgrade polars: similar row-counting bugs are often fixed in newer releases"],"exampleFix":"// before\nlet remaining = total.sub(consumed);\n// after\nlet remaining = if total.physical_rows >= consumed.physical_rows && total.deleted_rows >= consumed.deleted_rows {\n    total.sub(consumed)\n} else {\n    RowCounter { physical_rows: 0, deleted_rows: 0 } // or handle defensively\n};","handlingStrategy":"validation","validationCode":"fn can_sub(total: RowCounter, consumed: RowCounter) -> bool {\n    total.physical_rows >= consumed.physical_rows && total.deleted_rows >= consumed.deleted_rows\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Assert counter invariants (total >= consumed) near where slices are computed","Use checked_sub in your own slice arithmetic so underflows surface at the source","Keep RowCounter derivation and subtraction within the same range/chunk bookkeeping"],"tags":["rust","arithmetic","overflow","panic"],"backgroundTag":"value-out-of-range","analyzedSha":"fe841f959ef4d2ceefc05a310d33ed7b1ab24e5e","analyzedAt":"2026-09-18T22:14:11.667Z","contentChangedAt":"2026-09-18T22:14:11.667Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}