Skip to content

A short getting started on derive macros guide in Rust.

License

CC0-1.0, Unlicense licenses found

Licenses found

CC0-1.0
LICENSE-CC0
Unlicense
LICENSE-UNLICENSE
Notifications You must be signed in to change notification settings

1Git2Clone/proc-macro-example

Repository files navigation

Derive macro example

Build IconLicense CC0 IconLicense Unlicense Icon

A short getting started on derive macros guide in Rust.

Important

This assumes you're familiar with Rust's declarative macros. Or at least knowledgeable of programming language syntax and meaning of words like: identifier and expression.1 2 3 4 5

Table of Contents



What will be discussed

  1. A quick introduction to the three main types of declarative macros6:

    • Function-like - my_function_like_macro!(...) (Not to be confused with declarative macros).
    • Derive - #[derive(MyDeriveMacro)] (We'll focus on this one).
    • Attribute - #[my_attribute_macro].
  2. How to manage your thought process about the structuring of your crates and the structure of the macro.

  3. Understanding the process of creating a procedural macro and how it varies from declarative macros.

  4. Introduction to the common tools to achieve the task: the syn and quote crates.

Front-end, back-end and intermediate representation

Similarly to how compilers work, procedural macros work by taking some input, doing something with it, then generating new input for our main program. This is also the reason procedural macros need a separate crate in order to work.

Fortunately, most of the heavy lifting (parsing and generating Rust code) is already done with the help of syn and quote. This means that for Rust code generation, we can focus on the logic behind what we want to achieve more than making a parser for the complex syntax of the language.

Visual representation of syn being the front-end and quote as the back-end

  1. Syn handles the parsing (usually as syn::DeriveInput for derive macros).
  2. We work with the parsed data.
  3. Our work gets tokenized by the quote::quote! macro.

What will be done

An example project using a common pattern called: Reflective programming. The derive macro will work on structs and enums and add a method to them called: get_fields() for structs and get_variants() for enums.

"How does this reflect in the real world?"

Retrieving field names is a commonly used thing when we want to represent a Rust structure in a different format. This is the default way of serde (serde_derive/src/pretend.rs#64-76) to serialize your struct fields and enum variants.

Usage example

Here's an example of how serde would serialize a User object defined like so:

Code Example of a serde Serialze struct into JSON

use proc_macro_example_derive::Reflective;

#[derive(serde::Serialize, Reflective)]
struct User {
    pub user_id: u64,
    pub username: String,
    pub age: u32,
}

let some_user = User {
    user_id: 1234,
    username: String::from("Harry"),
    age: 41,
};
let fields = User::get_fields();
//           ^--------------- How convenient. This is a good example of how this macro can be used
//           to streamline the testing process.
let expected = format!(
    r#"{{"{}":{},"{}":"{}","{}":{}}}"#,
    fields[0], some_user.user_id, fields[1], &some_user.username, fields[2], some_user.age
);
assert_eq!(serde_json::to_string(&some_user).unwrap(), expected);

Insight and resources

If you've used yew, leptos or any other web development library, then you'd know you can parse any TokenStream into a Rust TokenStream. This example doesn't go that in-depth with what you can do since in practice it's possible to make a programming language with Rust's proc macros. In fact, people have also done that with a Python interpreter written in Rust or Python bindings in Rust.

The possibilities are endless and this example just scratches the surface of procedural macros.

Source code sources

Some other projects whose procedural macros you can check out are:

Video sources

License

This repository is public domain and dual licensed with the CC0 1.0 Universal license or The Unlicense lisence.

You're free to choose which one to use.

Footnotes

  1. https://doc.rust-lang.org/reference/macros-by-example.html#metavariables

  2. https://doc.rust-lang.org/reference/items.html

  3. https://doc.rust-lang.org/reference/attributes.html

  4. https://doc.rust-lang.org/reference/statements-and-expressions.html

  5. https://doc.rust-lang.org/reference/names.html

  6. https://doc.rust-lang.org/reference/procedural-macros.html#r-macro.proc.intro