DEV Community

TakaakiFuruse
TakaakiFuruse

Posted on • Edited on

Rust ProcMacro 101

Intro

You know this works....

fn greetings(var: String) { println!("{}", var) } greetings("Valar Morghulis!".to_string()); 

You know also this never works ....
If it works. It's a magic.

fn greetings_generator(var1: String, var2: String) { fn var2(var: String){ var1("{}", var) } } greeetings_generator(println!, greetings1) greetings1("Valar Morghulis!".to_string!) 

There's a way to make it work.
Use Rust macro.
Macro allows you to write Rust code as if you are dealing with function.

macro_rules! greetings_generator { ($var1:ident, $var2:ident) => { fn $var2(var: String) { $var1!("{}", var) } }; } greetings_generator!(print, greetings2); greetings2("Valar Morghulis from greeting2\n".to_string()); greetings_generator!(println, greetings3); greetings3("Valar Morghulis from greeting3".to_string()); 

Code on Rust Playground

Normal Approach

Now, let's order our Seven Gods whatever you want.

#[derive(Debug)] pub enum Gods { Father, Mother, Maiden, Crone, Warrior, Smith, Stranger, } fn my_gods_order(god:&Gods) -> u32{ match god { Gods::Father => 1, Gods::Mother => 2, Gods::Maiden => 3, Gods::Crone => 4, Gods::Warrior => 5, Gods::Smith => 6, Gods::Stranger => 7, } } fn your_gods_order(god:&Gods) -> u32{ match god { Gods::Father => 7, Gods::Mother => 6, Gods::Maiden => 5, Gods::Crone => 4, Gods::Warrior => 3, Gods::Smith => 2, Gods::Stranger => 1, } } fn main() { let mut gods = vec![Gods::Stranger, Gods::Father, Gods::Mother]; gods.sort_by(|a, b| my_gods_order(a).partial_cmp(&my_gods_order(b)).unwrap()); println!("{:?}", gods); let mut gods = vec![Gods::Smith, Gods::Crone, Gods::Stranger]; gods.sort_by(|a, b| your_gods_order(a).partial_cmp(&your_gods_order(b)).unwrap()); println!("{:?}", gods); } 

Use Macro

The more you have orders, you'll get the more and more match statemtns...
It works, but really annoying...

What you do....?

Macro...

That's Better...

macro_rules! gods_order1 { ($a:ident, $b:ident, $c:ident) => { fn my_gods_order1(var: &Gods) -> u32 { match var { Gods::$a => 1, Gods::$b => 2, Gods::$c => 3, _ => 100, } } }; } fn main() { gods_order1!(my_gods_order1, Stranger, Father, Mother); let mut gods = vec![Gods::Mother, Gods::Father, Gods::Stranger]; gods.sort_by(|a, b| my_gods_order1(a).partial_cmp(&my_gods_order1(&b)).unwrap()); println!("{:?}", gods); gods_order1!(your_gods_order1, Mother, Father, Stranger); let mut gods = vec![Gods::Mother, Gods::Father, Gods::Stranger]; gods.sort_by(|a, b| your_gods_order1(a).partial_cmp(&your_gods_order1(&b)).unwrap()); println!("{:?}", gods); } 

Even better...

macro_rules! gods_order2 { ($func:ident, [$(($elm:tt, $i:expr)),*]) => { fn $func(var: &Gods) -> u32 { match var { $(Gods::$elm => $i,)* _ => 0, } } }; } fn main() { gods_order2!(my_gods_order2, [(Stranger, 3), (Father, 2), (Mother, 1)]); let mut gods = vec![Gods::Mother, Gods::Father, Gods::Stranger]; gods.sort_by(|a, b| my_gods_order2(a).partial_cmp(&my_gods_order2(&b)).unwrap()); println!("{:?}", gods); gods_order2!(your_gods_order2, [(Mother, 1), (Father, 2), (Stranger, 3)]); let mut gods = vec![Gods::Mother, Gods::Father, Gods::Stranger]; gods.sort_by(|a, b| your_gods_order2(a).partial_cmp(&your_gods_order2(&b)).unwrap()); println!("{:?}", gods); } 

Code on Rust Playground

Use Proc Macro

Now, what if we can define order from strings.

Let's say order is pre-defined in config.toml and macro parse the order and returns defined order.

First, do cargo new enum_builder .

In src/config.toml

order="Stranger,Smith,Warrior,Crone,Maiden,Mother,Father" 

Since we use proc macro, we add followings in cargo.toml

[lib] proc-macro = true [dependencies] syn = { version = "0.15.42", features = ["extra-traits", "full"] } proc-macro2 = "0.4.30" quote = "0.6.12" toml = "0.4.2" 

Then, src/main.rs will be...

pub mod enum_builder { use enum_from_string::order; #[derive(Debug, order)] pub enum Gods { Father, Mother, Maiden, Crone, Warrior, Smith, Stranger, } } fn main() { assert_eq!(enum_builder::Gods::Father.order(), 6); assert_eq!(enum_builder::Gods::Stranger.order(), 0); } 

Finally, lib.rs is..

extern crate proc_macro; extern crate toml; use proc_macro::TokenStream; use quote::quote; use std::fs; use std::io::{BufReader, Read}; use toml::Value; use syn::{parse_macro_input, DeriveInput}; fn file_open(path: String) -> Result<String, String> { let mut file_content = String::new(); let mut fr = fs::File::open(path) .map(|f| BufReader::new(f)) .map_err(|e| e.to_string())?; fr.read_to_string(&mut file_content) .map_err(|e| e.to_string())?; Ok(file_content) } #[proc_macro_derive(order)] pub fn order_builder(item: TokenStream) -> TokenStream { let ast = parse_macro_input!(item as DeriveInput); let name = &ast.ident; let enum_variants = if let syn::Data::Enum(syn::DataEnum { variants, .. }) = ast.data { variants } else { unimplemented!(); }; let enum_len = &enum_variants.len(); let order_config: toml::Value = file_open("src/config.toml".to_string()) .unwrap() .parse::<Value>() .unwrap(); let orders: Vec<&str> = order_config["order"].as_str().unwrap().split(",").collect(); assert_eq!(&orders.len(), enum_len); let enum_fields = orders.iter().enumerate().map(|(i, elm)| { let elm_ident = syn::Ident::new(&elm, name.span()); let arm = quote! { #name::#elm_ident => #i as i32, }; arm }); let order_func = quote! { impl #name { pub fn order(&self) -> i32{ match self { #(#enum_fields)* } } } }; order_func.into() } 

Refs

For Proc macro basic...
https://github.com/azriel91/proc_macro_rules

For advanced proc macro learners
https://github.com/dtolnay/proc-macro-workshop

proc-macro-workshop has many fork repos and you can find good solutions, such as...
https://github.com/ELD/proc-macro-workshop
https://github.com/jonhoo/proc-macro-workshop
https://github.com/gobanos/proc-macro-workshop

Top comments (1)

Collapse
 
takaakifuruse profile image
TakaakiFuruse