{"record":{"id":"7408192277a4f536","repo":"linebender/druid","slug":"lens-implementations-cannot-be-derived-from-unions","errorCode":null,"errorMessage":"Lens implementations cannot be derived from unions","messagePattern":"Lens implementations cannot be derived from unions","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"druid-derive/src/lens.rs","lineNumber":19,"sourceCode":"// Copyright 2019 the Druid Authors\n// SPDX-License-Identifier: Apache-2.0\n\nuse super::attr::{FieldKind, Fields, LensAttrs};\nuse proc_macro2::{Ident, Span};\nuse quote::quote;\nuse std::collections::HashSet;\nuse syn::{spanned::Spanned, Data, GenericParam, TypeParam};\n\npub(crate) fn derive_lens_impl(\n    input: syn::DeriveInput,\n) -> Result<proc_macro2::TokenStream, syn::Error> {\n    match &input.data {\n        Data::Struct(_) => derive_struct(&input),\n        Data::Enum(e) => Err(syn::Error::new(\n            e.enum_token.span(),\n            \"Lens implementations cannot be derived from enums\",\n        )),\n        Data::Union(u) => Err(syn::Error::new(\n            u.union_token.span(),\n            \"Lens implementations cannot be derived from unions\",\n        )),\n    }\n}\n\nfn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> {\n    let ty = &input.ident;\n\n    let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &input.data {\n        Fields::<LensAttrs>::parse_ast(fields)?\n    } else {\n        return Err(syn::Error::new(\n            input.span(),\n            \"Lens implementations can only be derived from structs with named fields\",\n        ));\n    };\n","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/linebender/druid/blob/0f8b1195e4e073f9597f2865299c3d18f8e4005f/druid-derive/src/lens.rs#L1-L37","documentation":"The #[derive(Lens)] macro in druid-derive only supports deriving lens implementations from structs. When applied to a Rust union (or an enum), the macro emits a compile-time error anchored at the offending item's token span. Unions have no safe per-field access that a lens could wrap, so the derive deliberately rejects them.","triggerScenarios":"Writing `#[derive(Lens)]` on a `union` declaration, e.g. `#[derive(Lens)] union Foo { a: f32, b: u32 }`. The error is raised in derive_lens_impl when matching Data::Union.","commonSituations":"FFI-heavy code that models C memory layouts as unions and also wants data binding through lenses; copy-pasting a Lens derive onto an existing union type; IDE auto-completing a derive list onto the wrong item.","solutions":["Convert the union to a struct (or enum) with named fields if possible; unions with raw memory reinterpretation are incompatible with safe lenses.","Remove the `Lens` derive from the union and instead wrap it in a newtype struct (with named fields) that exposes the union's variants, and derive Lens on that wrapper.","Implement the Lens trait manually for a wrapper type around the union data.","If the union is only used for FFI, keep the union derive-free and place the Lens derive on the application-facing struct instead."],"exampleFix":"// before\n#[derive(Clone, Lens)]\nunion Value { int: i32, float: f32 }\n\n// after\n#[derive(Clone, Lens)]\nstruct ValueView { raw: Arc<Value>, as_float: bool } // named-field struct wraps the union\nimpl ValueView { fn get(&self) -> f64 { /* reinterpret */ } }","handlingStrategy":"validation","validationCode":"// compile-time: only attach #[derive(Lens)] to structs with named fields\n// static assertion pattern:\ntrait AssertNamedFieldStruct {}\nimpl<T> AssertNamedFieldStruct for T {}\n// union types cannot safely expose fields; keep them out of derive lists","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never list Lens in derives on enum or union types; grep derives before adding.","Keep FFI unions separate from UI state structs.","Use clippy/IDE hints to spot unions receiving derive attributes."],"tags":["rust","derive-macro","lens","compile-time"],"backgroundTag":"unsupported-operation","analyzedSha":"0f8b1195e4e073f9597f2865299c3d18f8e4005f","analyzedAt":"2026-09-10T14:27:34.582Z","contentChangedAt":"2026-09-10T14:27:34.582Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}