rust-lang/rust · critical

destructed mir constant of adt without variant idx

Error message

destructed mir constant of adt without variant idx

What it means

When pretty-printing a MIR constant whose type is a non-empty enum/struct ADT, rustc_middle destructures the constant via `tcx.try_destructure_mir_constant_for_user_output` and then `.expect`s the returned `contents.variant` is `Some`. The panic indicates the destructure succeeded (returned fields) but did not carry a variant index — an internal invariant violation in the const-eval / ty layer for ADT constants.

Source

Thrown at compiler/rustc_middle/src/mir/pretty.rs:1993

                        fmt.write_str("[")?;
                        comma_sep(tcx, fmt, fields)?;
                        fmt.write_str("]")?;
                    }
                    ty::Tuple(..) => {
                        fmt.write_str("(")?;
                        comma_sep(tcx, fmt, fields)?;
                        if contents.fields.len() == 1 {
                            fmt.write_str(",")?;
                        }
                        fmt.write_str(")")?;
                    }
                    ty::Adt(def, _) if def.variants().is_empty() => {
                        fmt.write_str(&format!("{{unreachable(): {ty}}}"))?;
                    }
                    ty::Adt(def, args) => {
                        let variant_idx = contents
                            .variant
                            .expect("destructed mir constant of adt without variant idx");
                        let variant_def = &def.variant(variant_idx);
                        let mut p = FmtPrinter::new(tcx, Namespace::ValueNS);
                        p.print_alloc_ids = true;
                        p.pretty_print_value_path(variant_def.def_id, args)?;
                        fmt.write_str(&p.into_buffer())?;

                        match variant_def.ctor_kind() {
                            Some(CtorKind::Const) => {}
                            Some(CtorKind::Fn) => {
                                fmt.write_str("(")?;
                                comma_sep(tcx, fmt, fields)?;
                                fmt.write_str(")")?;
                            }
                            None => {
                                fmt.write_str(" {{ ")?;
                                let mut first = true;
                                for (field_def, (ct, ty)) in iter::zip(&variant_def.fields, fields)
                                {

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Reproduce with `-Zmir-opt-level=0 --edition=2021` and `cargo clean` to rule out incr-comp corruption; if it disappears, clear `target/incremental`.
  2. Bisect MIR passes with `-Zdump-mir=all` to find which pass produced the offending ADT const, then report the pass + reduced repro to rustc.
  3. Avoid the triggering construct in your crate: replace the unusual const (transmuted enum, intrinsic-built ADT) with a normally-constructed value.
  4. On nightlies, try a few adjacent rustc dates to confirm whether it's a regression and attach the working/failing commit to the bug report.

Example fix

// before: const built via transmute loses its variant tag
const FLAG: MyEnum = unsafe { std::mem::transmute(0u8) };
// panic when -Zunpretty=mir renders the body

// after
const FLAG: MyEnum = MyEnum::A;
Defensive patterns

Strategy: try-catch

Validate before calling

// This is a .expect() during pretty-printing of a destructed ADT constant.
// If you synthesize ConstValue/Operand for ADTs, set the variant index first.
// For a constant you are about to pretty-print, check its shape:
use rustc_middle::mir::interpret::ConstValue;
use rustc_middle::mir::{Const, ConstOperand};
fn const_has_variant_for_adt<'tcx>(c: &Const<'tcx>, is_adt_ty: bool) -> bool {
    if !is_adt_ty { return true; }                 // only ADTs need a variant idx
    match c {
        Const::Val(_, _) | Const::Ty(_) => {
            // the variant index lives on the destructed user-facing Constant;
            // if you built it via ty::Const::from_, ensure .variant was set.
            true // structural check: see typeGuard
        }
        Const::Unevaluated(_) => true,
    }
}

Type guard

// When YOU construct the destructed constant for an ADT, guarantee variant_index
// is Some before it can reach the pretty-printer.
// Pseudo-shape of what the printer reads (adapt names to your rustc version):
//   struct DestructuredConstant<'tcx> { ty: Ty<'tcx>, variant: Option<VariantIdx>, fields: Vec<...> }
fn is_printable_adt_constant(variant: Option<rustc_abi::FieldIdx>, ty_is_adt: bool) -> bool {
    !ty_is_adt || variant.is_some() // ADT constants MUST carry a variant idx
}

Try / catch

// pretty-printing is best-effort; never let a malformed constant abort your tool.
use std::panic::{catch_unwind, AssertUnwindSafe};
let rendered = catch_unwind(AssertUnwindSafe(|| {
    format!("{:#?}", the_constant) // invokes the Debug/pretty path
}));
match rendered {
    Ok(s) => { /* use s */ }
    Err(_payload) => {
        // fall back to a lossy representation; do NOT re-pretty-print the same value
        format!("<unprintable constant of type {:?}>", ty)
    }
}

Prevention

When it happens

Trigger: Triggered during `write_mir_pretty` / constant rendering of a MIR body that contains an ADT-typed `Const` whose `ConstValue` is shape-compatible for destructure but the const system failed to attach a `VariantIdx`. Reproducible by feeding rustc a hand-constructed or post-pass MIR constant where the variant discriminant is missing.

Common situations: Internal rustc bug surfaced by an unusual const (e.g. an enum constructed via intrinsic/transmute, a const that survived an optimization that dropped the variant tag); nightly-only mir-opt or const-eval change; custom const-fold pass that emits ADT consts without a variant; corrupted incr-comp cache feeding a malformed const.

Related errors


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