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

  1. Remove the Decodable-family derive attribute from the `union` declaration; unions are not supported.
  2. If serialization is genuinely needed, change the `union` into an `enum` whose variants carry the alternatives, then keep the derive.
  3. 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

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


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