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

  1. Correct the offending `Erasable` impl: set `type Storage = [u8; size_of::<Self>()];` (or use the matching reference-tuple storage pattern shown for `&T`/`&[T]`).
  2. If the type's size genuinely changed, update its `Storage` declaration in lockstep and re-run `x.py test compiler/rustc_middle`.
  3. For generic types, prefer the parametric impls already in erase.rs (e.g. `impl<T> Erasable for &'_ T`) instead of a fixed-size array.
  4. 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

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


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