rust-lang/rust · error

cannot derive on union

Error message

cannot derive on union

What it means

`stable_hash_discriminant` is called by the `StableHash`/`StableHashNoContext` derives to emit (or skip) discriminant hashing. It handles enums and structs but explicitly `panic!`s on `syn::Data::Union` because hashing a union's active field is undefined without a runtime tag, and hashing raw bytes would leak uninitialized memory into the stable hash. This guard sits at stable_hash.rs:103.

Source

Thrown at compiler/rustc_macros/src/stable_hash.rs:103

            fn stable_hash<__Hcx: ::rustc_data_structures::stable_hash::StableHashCtxt>(
                &self,
                __hcx: &mut __Hcx,
                __hasher: &mut ::rustc_data_structures::stable_hash::StableHasher
            ) {
                #discriminant
                match *self { #body }
            }
        },
    )
}

fn stable_hash_discriminant(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {
    match s.ast().data {
        syn::Data::Enum(_) => quote! {
            ::std::mem::discriminant(self).stable_hash(__hcx, __hasher);
        },
        syn::Data::Struct(_) => quote! {},
        syn::Data::Union(_) => panic!("cannot derive on union"),
    }
}

fn stable_hash_body(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {
    s.each(|bi| {
        let attrs = parse_attributes(bi.ast());
        if attrs.ignore {
            quote! {}
        } else if let Some(project) = attrs.project {
            quote! {
                (&#bi.#project).stable_hash(__hcx, __hasher);
            }
        } else {
            quote! {
                #bi.stable_hash(__hcx, __hasher);
            }
        }
    })

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Remove the `StableHash`/`StableHashNoContext` derive from the union.
  2. Re-shape the union as an `enum` so a discriminant exists, then keep the derive.
  3. Implement `StableHash` by hand, hashing only an explicitly-tracked active-field tag plus that field's bytes, never the raw union storage.

Example fix

// before
#[derive(StableHash)]
union U {
    a: u32,
    b: u64,
}

// after
#[derive(StableHash)]
enum U {
    A(u32),
    B(u64),
}
Defensive patterns

Strategy: validation

Validate before calling

// Third 'cannot derive on union' site, this time from stable_hash.rs.
// The StableHash derive refuses unions because their hash depends on which
// field is active — there is no safe default. Pre-check before annotating.
fn reject_union_derive(items: &[(String, Item)]) -> Result<(), String> {
    for (name, item) in items {
        if matches!(item, Item::Union(_)) {
            return Err(format!(
                "`{name}` is a union; do not #[derive(StableHash)]. \
                 Implement HashStable manually, hashing a discriminator + \n\
                 the active field only.");
        }
    }
    Ok(())
}

Type guard

fn is_stable_hash_derivable(item: &syn::Item) -> bool {
    matches!(item, syn::Item::Struct(_) | syn::Item::Enum(_))
}

// Use: only emit #[derive(StableHash)] when is_stable_hash_derivable(&item).

Prevention

When it happens

Trigger: Applying `#[derive(StableHash)]` or `#[derive(StableHashNoContext)]` to a `union` item; the derive calls `stable_hash_discriminant`, which matches the `Union` arm and panics during macro expansion.

Common situations: Adding stable-hashing to a low-level union type during incr-comp refactoring. Bulk-applying a derive attribute block that includes `StableHash` to a type that was changed from struct to union. Migrating types between crates where one side still carries the derive.

Related errors


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