{"record":{"id":"c6a3af368137566f","repo":"GitoxideLabs/gitoxide","slug":"bug-instance-must-be-initialized-for-each-search","errorCode":null,"errorMessage":"BUG: instance must be initialized for each search set","messagePattern":"BUG: instance must be initialized for each search set","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-attributes/src/search/outcome.rs","lineNumber":227,"sourceCode":"                        .map(|attr| (attr.id, attr.inner.clone(), Some(id))),\n                );\n            }\n        }\n        false\n    }\n}\n\nimpl Outcome {\n    /// Given a list of `attrs` by order, return true if at least one of them is not set\n    pub(crate) fn has_unspecified_attributes(&self, mut attrs: impl Iterator<Item = AttributeId>) -> bool {\n        attrs.any(|order| self.matches_by_id[order.0].r#match.is_none())\n    }\n    /// Return the amount of attributes haven't yet been found.\n    ///\n    /// If this number reaches 0, then the search can be stopped as there is nothing more to fill in.\n    pub(crate) fn remaining(&self) -> usize {\n        self.remaining\n            .expect(\"BUG: instance must be initialized for each search set\")\n    }\n\n    fn reduce_and_check_if_done(&mut self, attr: AttributeId) -> bool {\n        if self.selected.is_empty() || self.selected.iter().any(|(_name, id)| *id == Some(attr)) {\n            *self.remaining.as_mut().expect(\"initialized\") -= 1;\n        }\n        self.is_done()\n    }\n}\n\nimpl std::fmt::Debug for Outcome {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        struct AsDisplay<'a>(&'a dyn std::fmt::Display);\n        impl std::fmt::Debug for AsDisplay<'_> {\n            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                self.0.fmt(f)\n            }\n        }","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-attributes/src/search/outcome.rs#L209-L245","documentation":"`Outcome::remaining()` returns the count of attributes not yet found, stored in an `Option<usize>` that is only set once `initialize()`/selection setup runs for a search set. The `.expect(\"BUG: instance must be initialized for each search set\")` panics when `remaining()` is called on an `Outcome` that was created but never initialized for the current attribute search set.","triggerScenarios":"Calling `Outcome::remaining()` (via the public search APIs that expose it) on an Outcome obtained without running a search/selection initialization pass — e.g. constructing an Outcome manually or reusing a stale Outcome across different search sets.","commonSituations":"Reusing a cached `Outcome` struct across multiple `gix-attributes` searches without re-initializing; custom code that mirrors gix's internal Outcome lifecycle when integrating the crate.","solutions":["Ensure the Outcome is produced by (or initialized through) the attribute search pipeline before reading `remaining()`","Re-create the Outcome for each search set instead of reusing an instance across searches","Upgrade gix — newer versions may make initialization implicit","If you control the code path, call the initializer before any `remaining()`/iteration use"],"exampleFix":"// before\nlet outcome = Outcome::default();\nlet left = outcome.remaining();\n// after: derive Outcome from the search\nlet outcome = collection.initialize(selected_attributes); // or obtain from search\nlet left = outcome.remaining();","handlingStrategy":"type-guard","validationCode":"// ensure the Outcome came from the search pipeline\nfn outcome_ready(o: &gix_attributes::search::Outcome) -> bool {\n    format!(\"{:?}\", o).contains(\"remaining: Some\") // Debug shows the Option\n}","typeGuard":"fn is_initialized(outcome: &Outcome) -> bool {\n    // initialize-then-use: only read remaining() right after a search produced it\n    std::ptr::eq(outcome as *const _, outcome as *const _) && !outcome_is_fresh(outcome)\n}","tryCatchPattern":"let left = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| outcome.remaining()));","preventionTips":["Never construct Outcome manually; obtain it from collection/search APIs","Re-create the Outcome for each search set","Do not cache Outcomes across attribute queries"],"tags":["rust","panic","uninitialized-state","attributes"],"backgroundTag":"invalid-state-transition","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}