{"id":"e4b9d46b3c52aec0","repo":"rust-lang/rust","slug":"cannot-derive-on-union-e4b9d4","errorCode":null,"errorMessage":"cannot derive on union","messagePattern":"cannot derive on union","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/rustc_macros/src/stable_hash.rs","lineNumber":103,"sourceCode":"            fn stable_hash<__Hcx: ::rustc_data_structures::stable_hash::StableHashCtxt>(\n                &self,\n                __hcx: &mut __Hcx,\n                __hasher: &mut ::rustc_data_structures::stable_hash::StableHasher\n            ) {\n                #discriminant\n                match *self { #body }\n            }\n        },\n    )\n}\n\nfn stable_hash_discriminant(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {\n    match s.ast().data {\n        syn::Data::Enum(_) => quote! {\n            ::std::mem::discriminant(self).stable_hash(__hcx, __hasher);\n        },\n        syn::Data::Struct(_) => quote! {},\n        syn::Data::Union(_) => panic!(\"cannot derive on union\"),\n    }\n}\n\nfn stable_hash_body(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {\n    s.each(|bi| {\n        let attrs = parse_attributes(bi.ast());\n        if attrs.ignore {\n            quote! {}\n        } else if let Some(project) = attrs.project {\n            quote! {\n                (&#bi.#project).stable_hash(__hcx, __hasher);\n            }\n        } else {\n            quote! {\n                #bi.stable_hash(__hcx, __hasher);\n            }\n        }\n    })","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_macros/src/stable_hash.rs#L85-L121","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n#[derive(StableHash)]\nunion U {\n    a: u32,\n    b: u64,\n}\n\n// after\n#[derive(StableHash)]\nenum U {\n    A(u32),\n    B(u64),\n}","handlingStrategy":"validation","validationCode":"// Third 'cannot derive on union' site, this time from stable_hash.rs.\n// The StableHash derive refuses unions because their hash depends on which\n// field is active — there is no safe default. Pre-check before annotating.\nfn reject_union_derive(items: &[(String, Item)]) -> Result<(), String> {\n    for (name, item) in items {\n        if matches!(item, Item::Union(_)) {\n            return Err(format!(\n                \"`{name}` is a union; do not #[derive(StableHash)]. \\\n                 Implement HashStable manually, hashing a discriminator + \\n\\\n                 the active field only.\");\n        }\n    }\n    Ok(())\n}","typeGuard":"fn is_stable_hash_derivable(item: &syn::Item) -> bool {\n    matches!(item, syn::Item::Struct(_) | syn::Item::Enum(_))\n}\n\n// Use: only emit #[derive(StableHash)] when is_stable_hash_derivable(&item).","tryCatchPattern":null,"preventionTips":["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."],"tags":["rustc-macros","derive","stable-hash","union"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}