{"id":"782623a4cd14f0f5","repo":"rust-lang/rust","slug":"size-of-t-must-match-erased-type-t-as-erasable","errorCode":null,"errorMessage":"size of T must match erased type <T as Erasable>::Storage","messagePattern":"size of T must match erased type <T as Erasable>::Storage","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"compiler/rustc_middle/src/query/erase.rs","lineNumber":79,"sourceCode":"///\n/// Using an opaque type alias allows the type checker to enforce that\n/// `Erased<T>` and `Erased<U>` are still distinct types, while allowing\n/// monomorphization to see that they might actually use the same storage type.\npub type Erased<T: Erasable> = ErasedData<impl Copy>;\n\n/// Erases a value of type `T` into `Erased<T>`.\n///\n/// `Erased<T>` and `Erased<U>` are type-checked as distinct types, but codegen\n/// can see whether they actually have the same storage type.\n#[inline(always)]\n#[define_opaque(Erased)]\n// The `DynSend` and `DynSync` bounds on `T` are used to\n// justify the safety of the implementations of these traits for `ErasedData`.\npub fn erase_val<T: Erasable + DynSend + DynSync>(value: T) -> Erased<T> {\n    // Ensure the sizes match\n    const {\n        if size_of::<T>() != size_of::<T::Storage>() {\n            panic!(\"size of T must match erased type <T as Erasable>::Storage\")\n        }\n    };\n\n    ErasedData::<<T as Erasable>::Storage> {\n        // `transmute_unchecked` is needed here because it does not have `transmute`'s size check\n        // (and thus allows to transmute between `T` and `MaybeUninit<T::Storage>`) (we do the size\n        // check ourselves in the `const` block above).\n        //\n        // `transmute_copy` is also commonly used for this (and it would work here since\n        // `Erasable: Copy`), but `transmute_unchecked` better explains the intent.\n        //\n        // SAFETY: It is safe to transmute to MaybeUninit for types with the same sizes.\n        data: unsafe { transmute_unchecked::<T, MaybeUninit<T::Storage>>(value) },\n        no_auto_traits: PhantomData,\n    }\n}\n\n/// Restores an erased value to its real type.","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_middle/src/query/erase.rs#L61-L97","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nimpl Erasable for MyResult<'_> {\n    type Storage = [u8; 16]; // stale: real size drifted to 24 after a field was added\n}\n\n// after\nimpl Erasable for MyResult<'_> {\n    type Storage = [u8; size_of::<Result<&'_ (), ErrorGuaranteed>>()];\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Erasable requires sizeof::<T>() == sizeof::<T as Erasable>::Storage().\n// Enforce it at compile time so the runtime assert never fires.\npub trait StrictErasable: erased_serde::Serialize {\n    const STORAGE_SIZE_OK: ();\n}\n\n#[macro_export]\nmacro_rules! impl_strict_erasable {\n    ($t:ty, $storage:ty) => {\n        const _: () = {\n            // Compile-time assert: sizes must match exactly.\n            assert!(::core::mem::size_of::<$t>() == ::core::mem::size_of::<$storage>());\n        };\n    };\n}\n\n// Usage at the definition site, not the call site:\n// impl_strict_erasable!(MyType, MyStorage);","tryCatchPattern":null,"preventionTips":["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."],"tags":["rustc","erasable","type-erasure","monomorphization","internal-compiler-error"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}