TakaakiFuruse
Posted on August 4, 2019
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());
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);
}
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
Posted on August 4, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 11, 2024