{"record":{"id":"da4d146463590174","repo":"gitbutlerapp/gitbutler","slug":"symbols-must-always-be-initialized","errorCode":null,"errorMessage":"symbols must always be initialized","messagePattern":"symbols must always be initialized","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"info","filePath":"crates/but/src/theme.rs","lineNumber":392,"sourceCode":"\n/// Helper — builds a bold + colored [`Style`].\nconst fn style_fg_bold(fg: Color) -> Style {\n    Style::new().fg(fg).add_modifier(Modifier::BOLD)\n}\n\nimpl Default for Theme {\n    /// Produces the canonical color palette.\n    fn default() -> Self {\n        Self::default_for(ThemePreset::Dark)\n    }\n}\n\nimpl Theme {\n    /// Get the symbols for this theme.\n    pub fn sym(&self) -> &ThemeSymbols {\n        self.symbols\n            .as_ref()\n            .expect(\"symbols must always be initialized\")\n    }\n\n    /// Produces a specific default color palette.\n    pub fn default_for(preset: ThemePreset) -> Self {\n        let mut t = match preset {\n            ThemePreset::Light => Self::default_light(),\n            ThemePreset::Dark => Self::default_dark(),\n        };\n        t.symbols = Some(ThemeSymbols::new(&t));\n        t\n    }\n\n    /// Load the syntax highlighting theme.\n    pub fn load_syntax_highlighting_theme(&self) -> anyhow::Result<highlighting::Theme> {\n        Ok(ThemeSet::load_from_reader(&mut std::io::Cursor::new(\n            self.syntax_highlighting_theme_raw,\n        ))?)\n    }","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/theme.rs#L374-L410","documentation":"Theme::sym() unwraps the private field symbols: Option<ThemeSymbols>. Every sanctioned constructor populates it: default_for() assigns t.symbols = Some(ThemeSymbols::new(&t)), Default delegates there, and the field is #[serde(skip_serializing, skip_deserializing)] while the container-level #[serde(default)] restores it from Theme::default(), so JSON-loaded themes also carry Some. The expect documents an internal invariant: a panic means a Theme was constructed bypassing these constructors.","triggerScenarios":"Only in-crate construction can leave symbols None - e.g. using default_light()/default_dark() output before default_for() fixes it up, or new code building/rebuilding a Theme struct literal that drops the field. Calling .sym() on such a value panics.","commonSituations":"Contributors adding new Theme constructors or partially cloning/rebuilding Theme values. Not triggerable by user theme files: serde round-trips keep symbols initialized.","solutions":["Construct themes only via Theme::default(), Theme::default_for(preset), or serde deserialization","When adding a constructor, finish it with t.symbols = Some(ThemeSymbols::new(&t)) exactly like default_for does","Long term: apply the refactor noted in the source comment - split symbols out of the deserialized struct so the Option disappears"],"exampleFix":"// before\nlet mut t = default_dark();\n// ... symbols still None here -> t.sym() would panic\n\n// after\nlet mut t = default_dark();\nt.symbols = Some(ThemeSymbols::new(&t)); // invariant restored before sym() is reachable","handlingStrategy":"validation","validationCode":"// Only sanctioned constructors keep the invariant; route all creation through them:\nlet theme = but::theme::Theme::default_for(preset); // symbols initialized\n// or deserialize - container-level #[serde(default)] restores symbols\nlet theme: but::theme::Theme = serde_json::from_str(json)?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never build Theme by struct literal or partial copy outside default_for/default_light/dark","End any new constructor with t.symbols = Some(ThemeSymbols::new(&t))","If refactoring Theme, split symbols into a separate struct to delete the Option entirely"],"tags":["rust","invariant","theme","serde","constructor","panic"],"backgroundTag":"unreachable-invariant-panic","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}