rust-lang/rust · critical
unreachable: invalid ExternAbi variant
Error message
unreachable: invalid ExternAbi variant
What it means
`ExternAbi::as_packed` linearly scans `ALL_VARIANTS` to find the calling-ABI variant's index, and this panic is its terminal 'no match' branch. Because every variant is enumerated in `ALL_VARIANTS` by the same macro that defines `as_packed`, a real `ExternAbi` value can never fail to match — the panic is pure defensive scaffolding for memory corruption or a future variant added without updating the list.
Source
Thrown at compiler/rustc_abi/src/extern_abi.rs:159
}
}
// FIXME(FnSigKind): when PartialEq is stably const, use it instead
const fn internal_const_eq(&self, other: &Self) -> bool {
match (self, other) {
$( ( $e_name::$variant $( { unwind: $uw } )* , $e_name::$variant $( { unwind: $uw } )* ) => true,)*
_ => false,
}
}
// ALL_VARIANTS.iter().position(|v| v == self), but const
pub const fn as_packed(&self) -> u8 {
let mut index = 0;
while index < $e_name::ALL_VARIANTS.len() {
if self.internal_const_eq(&$e_name::ALL_VARIANTS[index]) {
return index as u8;
}
index += 1;
}
panic!("unreachable: invalid ExternAbi variant");
}
pub const fn from_packed(index: u8) -> Self {
let index = index as usize;
assert!(index < $e_name::ALL_VARIANTS.len(), "invalid ExternAbi index");
$e_name::ALL_VARIANTS[index]
}
}
impl ::core::str::FromStr for $e_name {
type Err = AbiFromStrErr;
fn from_str(s: &str) -> Result<$e_name, Self::Err> {
match s {
$($tok => Ok($e_name::$variant $({ unwind: $uw })*),)*
_ => Err(AbiFromStrErr::Unknown),
}
}
}
}View on GitHub (pinned to 22057b88b0)
Solutions
- If you added a new ABI variant, ensure it is listed in the `abi_impls! { ExternAbi = { ... } }` block so both `ALL_VARIANTS` and the matches stay in sync.
- If this fires across a compiler version boundary, clear stale incremental/metadata caches and rebuild — `as_packed`/`from_packed` indices are not a stable format.
- Reproduce on stock rustc; if it ICEs, file a bug with the `extern "..."` item and rustc commit.
- Audit any unsafe code transmuting between `ExternAbi` and a numeric discriminant.
Example fix
// before — new variant declared on the enum but missing from abi_impls!
// (panics when serialized via as_packed)
// after
abi_impls! {
ExternAbi = {
// ...existing...
MyNewAbi =><= "my-new-abi",
}
} Defensive patterns
Strategy: validation
Validate before calling
// ExternAbi::as_packed() panics ('unreachable: invalid ExternAbi variant')
// if the value was not produced by a valid variant. The real risk is feeding an
// out-of-range u8 into from_packed() and then round-tripping. Bounds-check first.
use rustc_abi::ExternAbi;
fn valid_abi_packed_index(idx: u8) -> bool {
(idx as usize) < ExternAbi::ALL_VARIANTS.len()
}
// Prefer parsing a name rather than trusting a raw index:
fn abi_from_name(name: &str) -> Option<ExternAbi> {
name.parse::<ExternAbi>().ok()
} Type guard
fn is_known_extern_abi(abi: &ExternAbi) -> bool {
// ALL_VARIANTS is the authoritative set; this is sound because ExternAbi is
// non-exhaustive and a future variant added upstream must still round-trip.
ExternAbi::ALL_VARIANTS.iter().any(|v| v.internal_const_eq(abi))
} Try / catch
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| abi.as_packed()));
match result {
Ok(packed) => { /* store the u8 */ }
Err(_) => {
// The abi value was not a member of ALL_VARIANTS (e.g. constructed via
// unsafe transmute). Refuse to persist it; re-resolve from a name instead.
}
} Prevention
- Never construct ExternAbi by transmuting a u8 or by hand; obtain it via from_str / from_packed with a prior bounds check, or via ALL_VARIANTS.
- If you serialize an ExternAbi, store its as_str() name and re-parse on load — that route returns Result and degrades gracefully on unknown ABIs.
- Treat an out-of-range packed index as corrupt input data, not as a valid ABI; fail closed rather than indexing ALL_VARIANTS.
When it happens
Trigger: Calling `some_abi.as_packed()` (used to compactly serialize an ABI tag, e.g. in crate metadata or queries) on an `ExternAbi` value not present in the macro-generated `ALL_VARIANTS` list — something only possible via unsafe/transmute corruption, a partially-updated enum, or an exhaustive-match that went stale.
Common situations: A new ABI variant added to the enum but not to the `abi_impls!` list (or vice versa), deserializing an `as_packed` index from mismatched compiler versions, or memory/UB corruption that produced a discriminant outside the enum range (the earlier `assert!` in `from_packed` usually catches that first).
Related errors
- unsupported integer: {self:?}
- unsupported float: {self:?}
- `homogeneous_aggregate` should not be called for scalable ve
- aggregates can't have `FieldsShape::Primitive`
- assignment does not match variant
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/ab8b428101acad4d.json.
Report an issue: GitHub.