🎓 A1Learner
🔍

Rust Reading a Struct with syn

DeriveInput is the struct syn builds when it parses the item under a derive. It holds five fields, from attrs to data.

The five fields of DeriveInput

This is the struct syn hands you. The last lesson used only ident. The rest is all new:

pub struct DeriveInput {
    pub attrs: Vec<Attribute>,
    pub vis: Visibility,
    pub ident: Ident,
    pub generics: Generics,
    pub data: Data,
}

Match the data first

A derive can sit on a struct, an enum, or a union. This derive only wants structs. Anything else is an error:

let fields = match &ast.data {
    Data::Struct(data) => &data.fields,
    _ => return Err(syn::Error::new_spanned(ast, "FieldNames works on structs only")),
};

Named fields carry names

The Named shape holds a list of fields. Each field gives up its name. The unwrap is safe here. Named fields always have a name:

Fields::Named(named) => named
    .named
    .iter()
    .map(|field| field.ident.as_ref().unwrap())
    .collect::<Vec<_>>(),

One line per field

The #(...)* form repeats its block once per name. stringify! turns each name into text:

vec![#(stringify!(#names)),*]

The user side

The derive adds two methods to Config. The first one lists the fields:

#[derive(FieldNames)]
struct Config { host: String, port: u16, debug: bool }

println!("fields: {:?}", Config::field_names());

Example

// toolkit holds the FieldNames derive.
// Built and run with syn 2.0.119, quote 1.0.47, proc-macro2 1.0.107.
use toolkit::FieldNames;

#[derive(FieldNames)]
struct Config {
    host: String,
    port: u16,
    debug: bool,
}

fn main() {
    println!("fields: {:?}", Config::field_names());
    println!("count: {}", Config::field_count());
}

Key points

  • DeriveInput has five fields: attrs, vis, ident, generics, and data
  • data is an enum with three cases: Struct, Enum, and Union
  • Match on it to learn what you were given
  • A struct's fields come in three shapes: Named, Unnamed, and Unit
  • A tuple struct has the Unnamed shape
  • Each Field holds an ident and a ty
  • The ident is None for tuple fields
  • The ty is the field's type as tokens
  • quote! repeats a block with #(...)*
  • Feed it a list of fields
  • It writes one piece per field

In this example, the derive reads the Config struct at compile time. It matches the data and finds a struct. It matches the fields and finds the Named shape. It collects the three field names: host, port, and debug. Then quote! writes two methods for Config. The first method returns the names as a list of text. The second returns the count. The macro already knew the count while generating. Main prints the list and the count. The struct itself never changes.

Note

syn reads input behind features

The derive feature reads DeriveInput

The full feature also parses whole functions and items

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

A derive gets a tuple struct, struct Pair(i32, i32). What shape are its fields?