FuelLabs/fuels-rs · error · syn::Error

must have exactly one element

Error message

must have exactly one element

What it means

Compile-time error from the fuels macros crate: when deriving Fuel-typed ABI encoding for an enum, a variant with unnamed (tuple) fields must carry exactly one field. The derive walks each variant and treats a single unnamed field as the variant's payload type; anything else (0 fields is fine as Unit, but 2+ unnamed fields, or the error span on the parens) is rejected with this message.

Source

Thrown at packages/fuels-macros/src/parse_utils.rs:163

        Ok(Members {
            members,
            fuels_core_path,
        })
    }

    pub(crate) fn from_enum(data: DataEnum, fuels_core_path: TokenStream) -> syn::Result<Self> {
        let members = data
            .variants
            .into_iter()
            .map(|variant: Variant| {
                let name = variant.ident;
                if has_ignore_attr(&variant.attrs) {
                    Ok(Member::Ignored { name })
                } else {
                    let ty = match variant.fields {
                        Fields::Unnamed(fields_unnamed) => {
                            if fields_unnamed.unnamed.len() != 1 {
                                return Err(Error::new(
                                    fields_unnamed.paren_token.span.join(),
                                    "must have exactly one element",
                                ));
                            }
                            fields_unnamed.unnamed.into_iter().next()
                        }
                        Fields::Unit => None,
                        Fields::Named(named_fields) => {
                            return Err(Error::new_spanned(
                                named_fields,
                                "struct-like enum variants are not supported",
                            ));
                        }
                    }
                    .map(|field| field.ty.into_token_stream())
                    .unwrap_or_else(|| quote! {()});
                    Ok(Member::Normal { name, ty })
                }

View on GitHub (pinned to d9a250a518)

Solutions

  1. Reduce each tuple variant to exactly one field, wrapping extras in a struct: TooMany(Pair) where struct Pair { a: u64, b: bool } and derive on the struct too.
  2. If the extra field is a marker (PhantomData), move it into a wrapper struct instead of the variant.
  3. Alternatively switch the variant to unit-only and pass data through a separate type.
  4. Check for an #[ignore] attribute if the variant is intentionally not part of the ABI (has_ignore_attr skips it).

Example fix

// before
#[derive(FuelTypes)]
enum Status {
    Failed(String, u64),
}
// after
#[derive(FuelTypes)]
struct Failure {
    msg: String,
    code: u64,
}
#[derive(FuelTypes)]
enum Status {
    Failed(Failure),
}
Defensive patterns

Strategy: validation

Type guard

// compile-time 'guard': keep tuple variants single-field by construction
trait FuelAbiEnumShape {
    const OK: bool;
}
// convention: any enum used in ABI types has variants of form V(T) or V; reject V(T, U) in review
fn _assert_single_payload<T, U>() where U: FuelAbiEnumShape {}

Prevention

When it happens

Trigger: Applying #[derive(FuelTypes)]/the fuels type-generation derive (directly or via the macro pipeline in packages/fuels-macros) to an enum with a variant like TooMany(u64, bool) or NoFields() — i.e. Fields::Unnamed whose unnamed.len() != 1. Named-field variants fail separately ('struct-like enum variants are not supported'); unit variants are allowed.

Common situations: Porting a Rust enum with multi-field tuple variants to a Fuel contract type; modeling sum types the way serde/serde_json allow and expecting the same flexibility; adding a second field for metadata (e.g. MyVariant(Data, PhantomData)).

Related errors


AI-assisted analysis of FuelLabs/fuels-rs@d9a250a518 (2026-08-16). Data as JSON: /api/errors/a622febd5218b7e5. Report an issue: GitHub.