CodeBlanks Logo Icon CodeBlanks

derive macro

Write and test a custom derive macro.

Final task: derive macro

Design the code your own #[derive(Tanimla)] macro would generate: a tanim() method that writes the struct's name and each field with Display. In a real macro the fields are read with syn and this is generated with quote!:

impl Kisi {
    fn tanim(&self) -> String {
        let mut s = String::from("Kisi { ");
        s.push_str(&format!("ad: {}", self.ad));
        s.push_str(", ");
        s.push_str(&format!("yas: {}", self.yas));
        s.push_str(" }");
        s
    }
}

In the exercise we'll generate the same code with a macro_rules! taking the struct definition.

Generation with macro_rules!

The repetition pattern produces one piece per field; join places the separators:

macro_rules! tanimlanir {
    (struct $ad:ident { $($alan:ident : $tur:ty),* $(,)? }) => {
        struct $ad { $($alan: $tur),* }
        impl $ad {
            fn tanim(&self) -> String {
                let parcalar: Vec<String> = vec![
                    $(format!("{}: {}", stringify!($alan), self.$alan)),*
                ];
                format!("{} {{ {} }}", stringify!($ad), parcalar.join(", "))
            }
        }
    };
}

self.$alan generates a field access with the macro variable.

Practice Describable struct

Write the tanimlanir! macro above and generate the definition struct Kisi { ad: String, yas: u32 } with it. Print the result of Kisi { ad: "Ece".into(), yas: 30 }.tanim().

Starter code

main.rs
fn main() {
    // buraya yaz
}

Sample solution

main.rs
macro_rules! tanimlanir {
    (struct $ad:ident { $($alan:ident : $tur:ty),* $(,)? }) => {
        struct $ad {
            $($alan: $tur),*
        }
        impl $ad {
            fn tanim(&self) -> String {
                let parcalar: Vec<String> = vec![
                    $(format!("{}: {}", stringify!($alan), self.$alan)),*
                ];
                format!("{} {{ {} }}", stringify!($ad), parcalar.join(", "))
            }
        }
    };
}

tanimlanir!(struct Kisi { ad: String, yas: u32 });

fn main() {
    let k = Kisi { ad: "Ece".into(), yas: 30 };
    println!("{}", k.tanim());
}

Expected output

Kisi { ad: Ece, yas: 30 }

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 What is the easiest way to place the commas between fields correctly?

  • Write a comma after each field and delete the last
  • Not using commas
  • It isn't possible in a macro
  • Collect the pieces into a Vec<String> and join them with join(", ") Correct answer

Explanation: join solves the separator problem correctly, including the zero and single-element cases.

2 Generate field access

Question
$(format!("{}: {}", stringify!($alan), self.____)),*
Answer
$(format!("{}: {}", stringify!($alan), self.$alan)),*

Explanation: self.$alan generates a separate access for each field.

3 Piece and role

Term Meaning
stringify!($ad) Struct name
stringify!($alan) Field name
self.$alan Field value
join(", ") Separators

All lessons in Advanced