{"id":"e0047a3241d68572","repo":"rust-lang/rust","slug":"cannot-derive-on-union","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/serialize.rs","lineNumber":62,"sourceCode":"    decodable_body(s, decoder_ty)\n}\n\npub(super) fn decodable_nocontext_derive(\n    mut s: synstructure::Structure<'_>,\n) -> proc_macro2::TokenStream {\n    let decoder_ty = quote! { __D };\n    s.add_impl_generic(parse_quote! { #decoder_ty: ::rustc_serialize::Decoder });\n    s.add_bounds(synstructure::AddBounds::Fields);\n\n    decodable_body(s, decoder_ty)\n}\n\nfn decodable_body(\n    s: synstructure::Structure<'_>,\n    decoder_ty: TokenStream,\n) -> proc_macro2::TokenStream {\n    if let syn::Data::Union(_) = s.ast().data {\n        panic!(\"cannot derive on union\")\n    }\n    let ty_name = s.ast().ident.to_string();\n    let decode_body = match s.variants() {\n        [] => {\n            let message = format!(\"`{ty_name}` has no variants to decode\");\n            quote! {\n                panic!(#message)\n            }\n        }\n        [vi] => vi.construct(|field, _index| decode_field(field)),\n        variants => {\n            let match_inner: TokenStream = variants\n                .iter()\n                .enumerate()\n                .map(|(idx, vi)| {\n                    let construct = vi.construct(|field, _index| decode_field(field));\n                    quote! { #idx => { #construct } }\n                })","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_macros/src/serialize.rs#L44-L80","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n#[derive(Decodable)]\nunion U {\n    a: u32,\n    b: f32,\n}\n\n// after (option A: drop the derive)\nunion U {\n    a: u32,\n    b: f32,\n}\n\n// after (option B: switch to enum)\n#[derive(Decodable)]\nenum U {\n    A(u32),\n    B(f32),\n}","handlingStrategy":"validation","validationCode":"// Before slapping #[derive(Encode)] / #[derive(Decode)] on an item, confirm it is\n// NOT a union. These derives only support struct and enum. Unions must be\n// hand-implemented because their memory layout is not derivable.\n//\n// In build-script / CI lint that scans your crate's source:\nfn is_union(item: &syn::Item) -> bool {\n    matches!(item, syn::Item::Union(_))\n}\n\n// Use syn to parse each annotated item; if is_union(...) is true, reject the\n// derive and emit a clear diagnostic instead of letting the proc-macro panic:\n//   error: cannot derive Serialize on a union; implement it manually for `<name>`\nfor (name, is_un) in annotated_items {\n    if is_un {\n        return Err(format!(\n            \"cannot derive (Encode/Decode/Serialize) on union `{}`; \\n\\n\\\n             provide a manual impl or wrap the union in a struct\",\n            name\n        ));\n    }\n }","typeGuard":"// Narrow a parsed syntax node to the cases the derive supports.\nfn supports_derive(item: &syn::Item) -> bool {\n    matches!(item, syn::Item::Struct(_) | syn::Item::Enum(_))\n        && !matches!(item, syn::Item::Union(_))\n}\n\n// Usage guard in a custom lint or build check:\nif !supports_derive(&item) {\n    abort!(item, \"derive not supported on this item kind (union?)\");\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["rustc-macros","derive","serialization","union"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}