linebender/druid · error

Lens implementations cannot be derived from unions

Error message

Lens implementations cannot be derived from unions

What it means

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.

Solutions

  1. Convert the union to a struct (or enum) with named fields if possible; unions with raw memory reinterpretation are incompatible with safe lenses.
  2. 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.
  3. Implement the Lens trait manually for a wrapper type around the union data.
  4. If the union is only used for FFI, keep the union derive-free and place the Lens derive on the application-facing struct instead.

Example fix

// before
#[derive(Clone, Lens)]
union Value { int: i32, float: f32 }

// after
#[derive(Clone, Lens)]
struct ValueView { raw: Arc<Value>, as_float: bool } // named-field struct wraps the union
impl ValueView { fn get(&self) -> f64 { /* reinterpret */ } }
Defensive patterns

Strategy: validation

Validate before calling

// compile-time: only attach #[derive(Lens)] to structs with named fields
// static assertion pattern:
trait AssertNamedFieldStruct {}
impl<T> AssertNamedFieldStruct for T {}
// union types cannot safely expose fields; keep them out of derive lists

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10). Data as JSON: /api/errors/7408192277a4f536. Report an issue: GitHub.

Appendix: source

Thrown at druid-derive/src/lens.rs:19

// Copyright 2019 the Druid Authors
// SPDX-License-Identifier: Apache-2.0

use super::attr::{FieldKind, Fields, LensAttrs};
use proc_macro2::{Ident, Span};
use quote::quote;
use std::collections::HashSet;
use syn::{spanned::Spanned, Data, GenericParam, TypeParam};

pub(crate) fn derive_lens_impl(
    input: syn::DeriveInput,
) -> Result<proc_macro2::TokenStream, syn::Error> {
    match &input.data {
        Data::Struct(_) => derive_struct(&input),
        Data::Enum(e) => Err(syn::Error::new(
            e.enum_token.span(),
            "Lens implementations cannot be derived from enums",
        )),
        Data::Union(u) => Err(syn::Error::new(
            u.union_token.span(),
            "Lens implementations cannot be derived from unions",
        )),
    }
}

fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> {
    let ty = &input.ident;

    let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &input.data {
        Fields::<LensAttrs>::parse_ast(fields)?
    } else {
        return Err(syn::Error::new(
            input.span(),
            "Lens implementations can only be derived from structs with named fields",
        ));
    };

View on GitHub (pinned to 0f8b1195e4)