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
- Remove the `StableHash`/`StableHashNoContext` derive from the union.
- Re-shape the union as an `enum` so a discriminant exists, then keep the derive.
- 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
- A union's hash must reflect the active variant; the derive can't know it — never ask it to.
- Wrap unions you want to hash in a struct carrying an explicit discriminant, and hash the wrapper.
- Lint for `union` + `StableHash` in the same file.
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
- cannot derive on union
- error parsing stable_hash
- cannot derive on union
- cannot derive on union
- cannot derive on union
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/e4b9d46b3c52aec0.json.
Report an issue: GitHub.