rust-lang/rust · error
cannot derive on union
Error message
cannot derive on union
What it means
This panic is emitted by the body shared by all Decodable derive variants in rustc_macros (type_decodable, blob_decodable, lazy_decodable, decodable, decodable_nocontext). The derive machinery cannot generate field-by-field decode code for a Rust `union`, because union fields all overlap one storage region and have no discriminant, so there is no sound way to read variant-tagged bytes into them. The guard at serialize.rs:61 explicitly rejects `syn::Data::Union` before attempting to generate any decode body.
Source
Thrown at compiler/rustc_macros/src/serialize.rs:62
decodable_body(s, decoder_ty)
}
pub(super) fn decodable_nocontext_derive(
mut s: synstructure::Structure<'_>,
) -> proc_macro2::TokenStream {
let decoder_ty = quote! { __D };
s.add_impl_generic(parse_quote! { #decoder_ty: ::rustc_serialize::Decoder });
s.add_bounds(synstructure::AddBounds::Fields);
decodable_body(s, decoder_ty)
}
fn decodable_body(
s: synstructure::Structure<'_>,
decoder_ty: TokenStream,
) -> proc_macro2::TokenStream {
if let syn::Data::Union(_) = s.ast().data {
panic!("cannot derive on union")
}
let ty_name = s.ast().ident.to_string();
let decode_body = match s.variants() {
[] => {
let message = format!("`{ty_name}` has no variants to decode");
quote! {
panic!(#message)
}
}
[vi] => vi.construct(|field, _index| decode_field(field)),
variants => {
let match_inner: TokenStream = variants
.iter()
.enumerate()
.map(|(idx, vi)| {
let construct = vi.construct(|field, _index| decode_field(field));
quote! { #idx => { #construct } }
})View on GitHub (pinned to 22057b88b0)
Solutions
- Remove the Decodable-family derive attribute from the `union` declaration; unions are not supported.
- If serialization is genuinely needed, change the `union` into an `enum` whose variants carry the alternatives, then keep the derive.
- If the type must stay a union, implement `Decodable` manually by encoding/decoding an explicit tag that selects which field is active.
Example fix
// before
#[derive(Decodable)]
union U {
a: u32,
b: f32,
}
// after (option A: drop the derive)
union U {
a: u32,
b: f32,
}
// after (option B: switch to enum)
#[derive(Decodable)]
enum U {
A(u32),
B(f32),
} Defensive patterns
Strategy: validation
Validate before calling
// Before slapping #[derive(Encode)] / #[derive(Decode)] on an item, confirm it is
// NOT a union. These derives only support struct and enum. Unions must be
// hand-implemented because their memory layout is not derivable.
//
// In build-script / CI lint that scans your crate's source:
fn is_union(item: &syn::Item) -> bool {
matches!(item, syn::Item::Union(_))
}
// Use syn to parse each annotated item; if is_union(...) is true, reject the
// derive and emit a clear diagnostic instead of letting the proc-macro panic:
// error: cannot derive Serialize on a union; implement it manually for `<name>`
for (name, is_un) in annotated_items {
if is_un {
return Err(format!(
"cannot derive (Encode/Decode/Serialize) on union `{}`; \n\n\
provide a manual impl or wrap the union in a struct",
name
));
}
} Type guard
// Narrow a parsed syntax node to the cases the derive supports.
fn supports_derive(item: &syn::Item) -> bool {
matches!(item, syn::Item::Struct(_) | syn::Item::Enum(_))
&& !matches!(item, syn::Item::Union(_))
}
// Usage guard in a custom lint or build check:
if !supports_derive(&item) {
abort!(item, "derive not supported on this item kind (union?)");
} Prevention
- Never apply #[derive(Encode)], #[derive(Decode)], or #[derive(Serialize)] to a `union` — these macros panic on unions.
- Keep a `clippy::custom` lint or a CI grep that flags `#[derive(...)]` immediately above `union `, catching it before it reaches the compiler.
- Prefer wrapping the union inside a `struct` and deriving on the struct; implement the trait by hand on the wrapper if you need real semantics.
- If you genuinely need serialization on a union, write a manual `impl` that documents which field is active and validates it.
When it happens
Trigger: Annotating a `union` item with `#[derive(Decodable)]`, `#[derive(TypeDecodable)]`, `#[derive(BlobDecodable)]`, `#[derive(LazyDecodable)]`, or `#[derive(DecodableNoContext)]` (rustc-only derives); the proc-macro expands `decodable_body`, hits the `syn::Data::Union` match arm, and panics during macro expansion.
Common situations: Adding serialization derives to an FFI-style `union` type inside the compiler (e.g. wrapping `MaybeUninit`-like types). Copy-pasting derives from a nearby `struct`/`enum` onto a `union` during refactoring. Upgrading rustc internals where a struct was changed to a union without removing the derive.
Related errors
- cannot derive on union
- cannot derive on union
- cannot derive on union
- cannot derive on union
- error parsing stable_hash
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/e0047a3241d68572.json.
Report an issue: GitHub.