rust-lang/rust · critical

alt layout should always work

Error message

alt layout should always work

What it means

During struct layout, after a niche is found in the default (head-biased) layout, the compiler tries an end-biased alternative (`univariant_biased(..., NicheBias::End)`) to see if pushing the niche toward the tail leaves less padding. The comment 'alt layout should always work' reflects that the end-biased run uses the exact same fields, repr, and constraints as the already-successful head-biased run, so it cannot legitimately fail — an `Err` here means the biased variant has a bug.

Source

Thrown at compiler/rustc_abi/src/layout.rs:300

        // run and bias niches to the right and then check which one is closer to one of the
        // struct's edges.
        if let Ok(layout) = &layout {
            // Don't try to calculate an end-biased layout for unsizable structs,
            // otherwise we could end up with different layouts for
            // Foo<Type> and Foo<dyn Trait> which would break unsizing.
            if !matches!(kind, StructKind::MaybeUnsized) {
                if let Some(niche) = layout.largest_niche {
                    let head_space = niche.offset.bytes();
                    let niche_len = niche.value.size(dl).bytes();
                    let tail_space = layout.size.bytes() - head_space - niche_len;

                    // This may end up doing redundant work if the niche is already in the last
                    // field (e.g. a trailing bool) and there is tail padding. But it's non-trivial
                    // to get the unpadded size so we try anyway.
                    if fields.len() > 1 && head_space != 0 && tail_space > 0 {
                        let alt_layout = self
                            .univariant_biased(fields, repr, kind, NicheBias::End)
                            .expect("alt layout should always work");
                        let alt_niche = alt_layout
                            .largest_niche
                            .expect("alt layout should have a niche like the regular one");
                        let alt_head_space = alt_niche.offset.bytes();
                        let alt_niche_len = alt_niche.value.size(dl).bytes();
                        let alt_tail_space =
                            alt_layout.size.bytes() - alt_head_space - alt_niche_len;

                        debug_assert_eq!(layout.size.bytes(), alt_layout.size.bytes());

                        let prefer_alt_layout =
                            alt_head_space > head_space && alt_head_space > tail_space;

                        debug!(
                            "sz: {}, default_niche_at: {}+{}, default_tail_space: {}, alt_niche_at/head_space: {}+{}, alt_tail: {}, num_fields: {}, better: {}\n\
                            layout: {}\n\
                            alt_layout: {}\n",
                            layout.size.bytes(),

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Compare the `Start`- and `End`-bias arms of `univariant_biased` — they must be symmetric modulo the bias direction; restore any lost symmetry.
  2. Reproduce with the offending struct, dump both layouts via the existing `debug!` instrumentation in this function, and diff the offsets.
  3. Add a regression test mirroring the failing struct so the symmetry is enforced.
  4. If it reproduces on upstream rustc, file an ICE with the struct definition and `-Ztreat-err-as-bug` backtrace.

Example fix

// before — End-bias arm rejects an offset/alignment Start-bias accepts
match niche_bias {
    NicheBias::Start => compute(),
    NicheBias::End => return Err(...), // asymmetry -> panic
}

// after — both arms share one parameterized computation
compute(niche_bias)
Defensive patterns

Strategy: try-catch

Try / catch

// 'alt layout should always work' is a .expect() on the second niche-biased pass
// of univariant; by construction it should succeed whenever the first pass did.
// A panic here is a layout-calculator bug, so isolate univariant calls.
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    calc.univariant(&fields, &repr, kind)
}));
match result {
    Ok(Ok(layout)) => { /* use layout */ }
    Ok(Err(e))     => { /* report layout error to user */ }
    Err(payload)   => {
        let msg = payload
            .downcast_ref::<String>().map(|s| s.as_str())
            .or_else(|| payload.downcast_ref::<&'static str>().copied())
            .unwrap_or("univariant alt-layout panic");
        log::error!("layout calculator invariant violated: {msg}; fields={:?}", &fields);
        // fall back: retry with StructKind::Aligned so the caller still gets a layout
    }
}

Prevention

When it happens

Trigger: `LayoutCalculator::univariant_biased(fields, repr, kind, NicheBias::End)` returning `Err` in the niche-optimization fallback branch of `univariant`, when the earlier `univariant_biased(..., NicheBias::Start)` for the same inputs already succeeded.

Common situations: A change to `univariant_biased` that introduces an asymmetry between `NicheBias::Start` and `NicheBias::End` (e.g. an offset calculation that only works for head-bias), a niche/alignment edge case in a new target's data layout, or a refactor that made the end-bias path reject inputs the start-bias path accepts.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/49288306c628a8ab.json. Report an issue: GitHub.