{"id":"ec35c701de775a5b","repo":"rust-lang/rust","slug":"alt-layout-should-have-a-niche-like-the-regular-on","errorCode":null,"errorMessage":"alt layout should have a niche like the regular one","messagePattern":"alt layout should have a niche like the regular one","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"compiler/rustc_abi/src/layout.rs","lineNumber":303,"sourceCode":"            // Don't try to calculate an end-biased layout for unsizable structs,\n            // otherwise we could end up with different layouts for\n            // Foo<Type> and Foo<dyn Trait> which would break unsizing.\n            if !matches!(kind, StructKind::MaybeUnsized) {\n                if let Some(niche) = layout.largest_niche {\n                    let head_space = niche.offset.bytes();\n                    let niche_len = niche.value.size(dl).bytes();\n                    let tail_space = layout.size.bytes() - head_space - niche_len;\n\n                    // This may end up doing redundant work if the niche is already in the last\n                    // field (e.g. a trailing bool) and there is tail padding. But it's non-trivial\n                    // to get the unpadded size so we try anyway.\n                    if fields.len() > 1 && head_space != 0 && tail_space > 0 {\n                        let alt_layout = self\n                            .univariant_biased(fields, repr, kind, NicheBias::End)\n                            .expect(\"alt layout should always work\");\n                        let alt_niche = alt_layout\n                            .largest_niche\n                            .expect(\"alt layout should have a niche like the regular one\");\n                        let alt_head_space = alt_niche.offset.bytes();\n                        let alt_niche_len = alt_niche.value.size(dl).bytes();\n                        let alt_tail_space =\n                            alt_layout.size.bytes() - alt_head_space - alt_niche_len;\n\n                        debug_assert_eq!(layout.size.bytes(), alt_layout.size.bytes());\n\n                        let prefer_alt_layout =\n                            alt_head_space > head_space && alt_head_space > tail_space;\n\n                        debug!(\n                            \"sz: {}, default_niche_at: {}+{}, default_tail_space: {}, alt_niche_at/head_space: {}+{}, alt_tail: {}, num_fields: {}, better: {}\\n\\\n                            layout: {}\\n\\\n                            alt_layout: {}\\n\",\n                            layout.size.bytes(),\n                            head_space,\n                            niche_len,\n                            tail_space,","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_abi/src/layout.rs#L285-L321","documentation":"Immediately after the end-biased alternative layout succeeds, the code assumes it contains a niche at least as usable as the head-biased one (`largest_niche` is `Some`). The premise is that the alternative run was only attempted because the regular layout *had* a niche and used the identical fields; end-biasing reorders but should not eliminate the niche. A missing niche here means the biasing changed which field exposes the niche — a bug in `univariant_biased`.","triggerScenarios":"`univariant`'s niche-optimization fallback branch calling `univariant_biased(..., NicheBias::End)`, getting `Ok(alt_layout)` where `alt_layout.largest_niche` is `None`, while the head-biased layout had `largest_niche = Some(..)`.","commonSituations":"A change to niche detection that depends on field order (so end-biasing reorders fields and loses the niche), a `repr`/`align` combination where the niche falls in padding only under one bias, or a refactor of `Niche` construction that forgot to populate `largest_niche` on the biased path.","solutions":["Ensure `univariant_biased` populates `largest_niche` identically for both `NicheBias::Start` and `NicheBias::End` — the niche value depends on the field, not the bias.","Reproduce with the offending struct and dump `largest_niche` for both layouts via the existing `debug!` call to find which field lost its niche under end-bias.","Guard with a `debug_assert!` upstream so asymmetry is caught closer to the source.","File an ICE with the struct definition if it reproduces on upstream rustc."],"exampleFix":"// before — niche only set under Start bias\nlet largest_niche = if niche_bias == NicheBias::Start { find_niche() } else { None };\n\n// after — niche is bias-independent\nlet largest_niche = find_niche();","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// 'alt layout should have a niche like the regular one' is a .expect() asserting\n// that the end-biased univariant pass produced a niche when the start-biased one\n// did. A panic means the niche-finding logic is inconsistent; isolate and fall\n// back to the start-biased layout (which the code already had).\nlet result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    calc.univariant(&fields, &repr, kind)\n}));\nmatch result {\n    Ok(Ok(layout)) => { /* primary (start-biased) layout is fine; use it */ }\n    Ok(Err(e))     => { /* surface layout error */ }\n    Err(payload)   => {\n        log::error!(\n            \"niche invariant violated on alt layout: {}\",\n            payload.downcast_ref::<String>().map(|s| s.as_str())\n                .or_else(|| payload.downcast_ref::<&'static str>().copied())\n                .unwrap_or(\"?\")\n        );\n        // The start-biased pass is the source of truth; if even that panicked,\n        // recompute with niche optimization disabled (repr without niche).\n    }\n}","preventionTips":["Both [8] and [9] stem from the niche-biasing comparison inside univariant; they cannot be pre-validated by the caller, so defense = isolation + fallback, not input filtering.","Keep the start-biased layout available as the authoritative fallback; the end-biased (alt) pass is only an optimization for niche placement near struct edges.","When you hit this on a specific aggregate, record the field types and their niches — the bug is almost always a niche that the start pass found but the end pass dropped, so that field set is the minimization target."],"tags":["rustc","struct-layout","niche-optimization","ice"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}