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
- Compare the `Start`- and `End`-bias arms of `univariant_biased` — they must be symmetric modulo the bias direction; restore any lost symmetry.
- Reproduce with the offending struct, dump both layouts via the existing `debug!` instrumentation in this function, and diff the offsets.
- Add a regression test mirroring the failing struct so the symmetry is enforced.
- 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
- This is an internal invariant of LayoutCx::univariant, not a precondition a caller controls; treat any trip as a bug and capture the field list + repr for reproduction.
- When computing layouts for many types in batch (codegen, miri), wrap each univariant call in catch_unwind so a niche-biasing regression does not abort the whole pipeline.
- If you hit it consistently on one type, simplify that type (remove niche-bearing trailing fields) to confirm the regression is in niche placement, then report the minimized case.
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
- alt layout should have a niche like the regular one
- unsupported integer: {self:?}
- unsupported float: {self:?}
- `homogeneous_aggregate` should not be called for scalable ve
- aggregates can't have `FieldsShape::Primitive`
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/49288306c628a8ab.json.
Report an issue: GitHub.