rust-lang/rust · critical
size of T must match erased type <T as Erasable>::Storage
Error message
size of T must match erased type <T as Erasable>::Storage
What it means
This is a `const {}` compile-time check inside `erase_val` (erase.rs:79) that asserts `size_of::<T>() == size_of::<T::Storage>()` for every type implementing `Erasable`. The erased-storage scheme (used to cut monomorphization cost inside the query cache) is only sound when the declared `Storage` byte-array exactly fits the real type. The panic is evaluated at monomorphization time, so a wrong `Erasable` impl fails the build the first time that `T` is erased.
Source
Thrown at compiler/rustc_middle/src/query/erase.rs:79
///
/// Using an opaque type alias allows the type checker to enforce that
/// `Erased<T>` and `Erased<U>` are still distinct types, while allowing
/// monomorphization to see that they might actually use the same storage type.
pub type Erased<T: Erasable> = ErasedData<impl Copy>;
/// Erases a value of type `T` into `Erased<T>`.
///
/// `Erased<T>` and `Erased<U>` are type-checked as distinct types, but codegen
/// can see whether they actually have the same storage type.
#[inline(always)]
#[define_opaque(Erased)]
// The `DynSend` and `DynSync` bounds on `T` are used to
// justify the safety of the implementations of these traits for `ErasedData`.
pub fn erase_val<T: Erasable + DynSend + DynSync>(value: T) -> Erased<T> {
// Ensure the sizes match
const {
if size_of::<T>() != size_of::<T::Storage>() {
panic!("size of T must match erased type <T as Erasable>::Storage")
}
};
ErasedData::<<T as Erasable>::Storage> {
// `transmute_unchecked` is needed here because it does not have `transmute`'s size check
// (and thus allows to transmute between `T` and `MaybeUninit<T::Storage>`) (we do the size
// check ourselves in the `const` block above).
//
// `transmute_copy` is also commonly used for this (and it would work here since
// `Erasable: Copy`), but `transmute_unchecked` better explains the intent.
//
// SAFETY: It is safe to transmute to MaybeUninit for types with the same sizes.
data: unsafe { transmute_unchecked::<T, MaybeUninit<T::Storage>>(value) },
no_auto_traits: PhantomData,
}
}
/// Restores an erased value to its real type.View on GitHub (pinned to 22057b88b0)
Solutions
- Correct the offending `Erasable` impl: set `type Storage = [u8; size_of::<Self>()];` (or use the matching reference-tuple storage pattern shown for `&T`/`&[T]`).
- If the type's size genuinely changed, update its `Storage` declaration in lockstep and re-run `x.py test compiler/rustc_middle`.
- For generic types, prefer the parametric impls already in erase.rs (e.g. `impl<T> Erasable for &'_ T`) instead of a fixed-size array.
- Run `cargo +nightly build -vv` to surface which `T` monomorphized `erase_val` and tripped the const check.
Example fix
// before
impl Erasable for MyResult<'_> {
type Storage = [u8; 16]; // stale: real size drifted to 24 after a field was added
}
// after
impl Erasable for MyResult<'_> {
type Storage = [u8; size_of::<Result<&'_ (), ErrorGuaranteed>>()];
} Defensive patterns
Strategy: type-guard
Type guard
// Erasable requires sizeof::<T>() == sizeof::<T as Erasable>::Storage().
// Enforce it at compile time so the runtime assert never fires.
pub trait StrictErasable: erased_serde::Serialize {
const STORAGE_SIZE_OK: ();
}
#[macro_export]
macro_rules! impl_strict_erasable {
($t:ty, $storage:ty) => {
const _: () = {
// Compile-time assert: sizes must match exactly.
assert!(::core::mem::size_of::<$t>() == ::core::mem::size_of::<$storage>());
};
};
}
// Usage at the definition site, not the call site:
// impl_strict_erasable!(MyType, MyStorage); Prevention
- This assert fires inside rustc's type erasure for queries; it indicates the erased type's Storage has a different size than T.
- If you hit it, audit any type you feed into erased APIs (e.g. erased_serde, dyn-clone style erasure) for size mismatches across crate versions.
- Add a `const _: () = assert!(size_of::<T>() == size_of::<Storage>());` next to your Erasable impl to surface it at compile time.
- Recompile dependents when a type's size/alignment changes so stale encoded blobs are not decoded against a new layout.
When it happens
Trigger: Triggered when a developer adds a new `impl Erasable for SomeType { type Storage = [u8; N]; }` with `N != size_of::<SomeType>()`, or changes the layout of a type whose existing `Erasable` impl hard-codes the old size, and then routes that type through a query whose result gets erased via `erase_val`. The `const` block turns it into a monomorphization-time panic rather than a runtime one.
Common situations: Seen by rustc contributors and tooling authors extending the query system: adding a new query returning a type, modifying a type in `rustc_middle::ty`/`mir` that already has an `Erasable` impl without bumping its `Storage` size, or refactoring the `impl_erasable_for_types_with_no_type_params!` macro list. End users of stable Rust cannot reach this path at all.
Related errors
- internal error: entered unreachable code
- CGU symbol name accessed before setting
- non-empty waiters vec
- Incremental cache file size overflowed u64.
- cannot decode `AttrId` with `CacheDecoder`
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/782623a4cd14f0f5.json.
Report an issue: GitHub.