influxdata/influxdb · error · syn::Error

catalog records cannot be generic — the persisted bytes…

Error message

catalog records cannot be generic — the persisted bytes must be one fixed shape

What it means

This is a compile-time error from the influxdb3_catalog_macros proc-macro (`#[catalog_record]`). Catalog record structs are serialized to bytes via bitcode with a single fixed wire shape, so the macro rejects any struct declared with generic parameters (`struct Foo<T>`) because generics would produce multiple possible persisted shapes. The check happens in `expand` before any fingerprinting.

Solutions

  1. Remove the generic parameters from the struct and use concrete types for all fields.
  2. If several concrete variants are needed, declare one non-generic record struct per variant.
  3. If type abstraction is required, store a wire type declared in `records::types` and convert to the runtime type in the `apply` method.

Example fix

// before
#[catalog_record(shape = 0x1234)]
struct NodeRecord<T> { id: T }

// after
#[catalog_record(shape = 0x1234)]
struct NodeRecord { id: u64 }
Defensive patterns

Strategy: type-guard

Validate before calling

// Compile-time: assert the struct is non-generic before annotating
// macro_rules guard not possible for generics; instead ensure:
struct MyRecord { /* concrete fields only */ }
const _: () = { let _ = core::mem::size_of::<MyRecord>; };

Type guard

fn is_non_generic<T>() -> bool { true } // if it compiles with concrete args, the record type has no free generics

Prevention

When it happens

Trigger: Applying the `#[catalog_record(...)]` attribute macro to a struct that declares generic parameters, e.g. `#[catalog_record(shape = 0x...)] struct MyRecord<T> { field: T }`. Raised during compilation by `expand`, invoked from `catalog_record`.

Common situations: A developer tries to make a catalog record reusable over several value types, or wraps an existing generic container type as a record, or adds a generic parameter while refactoring field types.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of influxdata/influxdb@06200ef96b (2026-09-19). Data as JSON: /api/errors/d13eab88ad4b6783. Report an issue: GitHub.

Appendix: source

Thrown at influxdb3_catalog_macros/src/lib.rs:155

                proc_macro2::Span::call_site(),
                "missing `shape = 0x…` — build with a placeholder to be told the expected value",
            )
        })?;

        Ok(Self {
            id,
            shape: shape.base10_parse()?,
            shape_span: shape.span(),
            flags,
        })
    }
}

fn expand(metas: Punctuated<Meta, Token![,]>, item: ItemStruct) -> syn::Result<TokenStream2> {
    let args = Args::parse(metas)?;

    if !item.generics.params.is_empty() {
        return Err(syn::Error::new(
            item.generics.span(),
            "catalog records cannot be generic — the persisted bytes must be one fixed shape",
        ));
    }

    let Fields::Named(ref fields) = item.fields else {
        return Err(syn::Error::new(
            item.fields.span(),
            "catalog records must be structs with named fields",
        ));
    };

    let field_types: Vec<&Type> = fields.named.iter().map(|f| &f.ty).collect();
    for ty in &field_types {
        types::check(ty)?;
    }

    let actual = shape::fingerprint(&field_types);

View on GitHub (pinned to 06200ef96b)