bevyengine/bevy · error · syn::Error

#[{meta}] only supports structs, not unions

Error message

#[{meta}] only supports structs, not unions

What it means

Companion of the enum case: get_struct_fields rejects unions when a struct-only Bevy derive macro is applied to a `union` item. The error span points at the `union` keyword. Bevy derives need per-field access to generate code (component storage, param fetching), which unions cannot provide safely.

Source

Thrown at crates/bevy_macro_utils/src/shape.rs:17

use syn::{
    punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataEnum, DataUnion, Error,
    Field, Fields,
};

/// Get the fields of a data structure if that structure is a struct;
/// otherwise, return a compile error that points to the site of the macro invocation.
///
/// `meta` should be the name of the macro calling this function.
pub fn get_struct_fields<'a>(data: &'a Data, meta: &str) -> Result<&'a Fields, Error> {
    match data {
        Data::Struct(data_struct) => Ok(&data_struct.fields),
        Data::Enum(DataEnum { enum_token, .. }) => Err(Error::new(
            enum_token.span(),
            format!("#[{meta}] only supports structs, not enums"),
        )),
        Data::Union(DataUnion { union_token, .. }) => Err(Error::new(
            union_token.span(),
            format!("#[{meta}] only supports structs, not unions"),
        )),
    }
}

/// Return an error if `Fields` is not `Fields::Named`
pub fn require_named<'a>(fields: &'a Fields) -> Result<&'a Punctuated<Field, Comma>, Error> {
    if let Fields::Named(fields) = fields {
        Ok(&fields.named)
    } else {
        Err(Error::new(
            fields.span(),
            "Unnamed fields are not supported here",
        ))
    }
}

View on GitHub (pinned to 396ca72708)

Solutions

  1. Move the derive off the union onto a plain struct wrapper.
  2. Replace the union with a struct for ECS data; keep unions in FFI boundary code only.
  3. Delete the stale derive if it was attached accidentally.

Example fix

// before
#[derive(Bundle)]
union RawInput {
    bytes: [u8; 8],
    fields: (u32, u32),
}

// after
#[derive(Bundle)]
struct InputBundle {
    raw: RawInput, // union kept as a plain field, no derive on it
}
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Applying #[derive(Bundle)], #[derive(SystemParam)], #[derive(QueryData)], #[derive(WorldQuery)], or the render Specializer derive to a Rust `union` item.

Common situations: FFI or zero-copy code where a union was declared in the same module as Bevy types and a derive was attached by copy-paste or IDE auto-complete.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/2c371088e3e1da4f. Report an issue: GitHub.