Course contentsShow
← Rust
Lesson 1597 of 1,91013. Metaprogramming with MacrosFree lesson

Code Generation for Heterogeneous Data

You will develop generic macros that process arbitrary union and structure types consistently.

In this lesson, you will learn how to build procedural macros that unify the transformation logic for both structs and enums, treating heterogeneous data shapes as uniform abstract syntax trees (ASTs).

When generating code for complex data models, you often face the "structure vs. variant" divide. A struct contains named or positional fields, while an enum contains variants that may themselves contain data. Writing separate macros for these is a maintenance burden. Instead, we can normalize these types into a unified internal representation during the token-stream parsing phase.

Normalizing Data Shapes

The secret to processing heterogeneous data is to extract an intermediate representation that maps both Fields (from structs) and Variants (from enums) into a common format:

// Our normalized internal type
struct DataEntry {
    name: String,
    fields: Vec<FieldInfo>,
}

// Inside the derive macro
fn generate_logic(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    let normalized = match input.data {
        Data::Struct(s) => vec![DataEntry::from_fields(s.fields)],
        Data::Enum(e) => e.variants.into_iter().map(DataEntry::from_variant).collect(),
        _ => panic!("Unsupported type"),
    };

    // Now, your logic operates uniformly on a Vec<DataEntry>
    let expanded = normalized.iter().map(|entry| generate_for_entry(entry));
    
    quote! { #(#expanded)* }.into()
}

Why This Matters

By abstracting the input into a DataEntry collection, your code generation logic (such as serialization, validation, or trait synthesis) remains oblivious to whether the original type was a record or a sum type. This approach is highly resilient; if you decide to add a new union support, you only need to update the normalization layer—the rest of your generator remains untouched.

This pattern enforces a single source of truth for your data invariants. Whether you are generating From implementations or custom JSON mappers, you define the mapping logic once, ensuring that your generated code is consistent across your entire domain model, regardless of the underlying data layout.

Key Takeaway: By normalizing heterogeneous data into a unified internal representation before triggering code generation, you eliminate redundant logic and ensure structural consistency across disparate data types.