Ce face macro_rules!
Un macro declarativ primește tokeni (bucăți de cod), îi compară cu tiparele din ramuri, de sus în jos, și înlocuiește apelul cu codul din prima ramură potrivită. Se apelează cu !: salut!(), maxim!(3, 7, 5). Spre deosebire de o funcție, poate primi un număr variabil de argumente și poate genera orice cod (funcții, structuri, implementări).
În tipare, $nume:fel capturează o bucată de cod de un anumit fel: expr (expresie), ident (nume), ty (tip), tt (un token), literal, pat, block. Repetițiile se scriu $( ... ),* (zero sau mai multe, separate prin virgulă) sau $( ... ),+ (cel puțin una); în codul generat, $( ... )* repetă pentru fiecare element capturat. $(,)? permite o virgulă la final.
Un macro trebuie definit înainte de locul unde este folosit în fișier. Pentru a-l folosi din alt modul sau crate se marchează cu #[macro_export]. Folosește macro-uri doar când o funcție nu ajunge (număr variabil de argumente, cod repetitiv pentru mai multe tipuri); erorile din macro-uri sunt mai greu de înțeles.
Exemple
Exemplul 1: Macro cu mai multe ramuri și număr variabil de argumente
maxim! are două ramuri: una pentru un singur argument și una recursivă pentru mai multe. Funcționează cu orice tip comparabil.
macro_rules! maxim {
($x:expr) => { $x };
($x:expr, $($rest:expr),+) => {{
let a = $x;
let b = maxim!($($rest),+);
if a > b { a } else { b }
}};
}
fn main() {
println!("{}", maxim!(4));
println!("{}", maxim!(3, 9, 7, 5));
println!("{}", maxim!(2.5, 1.0));
println!("{}", maxim!("Arad", "Iași", "Bacău"));
}
Rezultat
4
9
2.5
Iași
Exemplul 2: Macro care construiește o colecție
dictionar! primește perechi cheie => valoare și generează câte un insert pentru fiecare, la fel cum vec! generează un push pentru fiecare element. $(,)? acceptă și virgula de la final.
use std::collections::BTreeMap;
macro_rules! dictionar {
($($cheie:expr => $valoare:expr),* $(,)?) => {{
let mut harta = BTreeMap::new();
$( harta.insert($cheie, $valoare); )*
harta
}};
}
fn main() {
let prefixe = dictionar! {
"Iași" => "0232",
"Cluj" => "0264",
"București" => "021",
};
println!("{prefixe:?}");
println!("Prefix Cluj: {}", prefixe["Cluj"]);
}
Rezultat
{"București": "021", "Cluj": "0264", "Iași": "0232"}
Prefix Cluj: 0264
Exemplul 3: Macro care generează funcții
$nume:ident capturează un nume, folosit apoi ca nume de funcție. Un singur apel al macro-ului creează trei funcții de conversie.
macro_rules! conversie {
($($nume:ident: $factor:expr),*) => {
$(
fn $nume(valoare: f64) -> f64 {
valoare * $factor
}
)*
};
}
conversie!(km_in_mile: 0.621371, kg_in_livre: 2.20462, lei_in_euro: 1.0 / 4.97);
fn main() {
println!("100 km = {:.1} mile", km_in_mile(100.0));
println!("70 kg = {:.1} livre", kg_in_livre(70.0));
println!("500 lei = {:.2} euro", lei_in_euro(500.0));
}
Rezultat
100 km = 62.1 mile
70 kg = 154.3 livre
500 lei = 100.60 euro
Greșeli frecvente
- Folosești macro-ul înainte de definiție în același fișier:
cannot find macro `...` in this scope. Mutămacro_rules!mai sus. - Uiți acoladele duble când macro-ul trebuie să producă o expresie cu mai multe instrucțiuni. Primele acolade aparțin sintaxei ramurii; al doilea rând creează blocul care devine valoarea.
- Folosești
$xde mai multe ori în codul generat: expresia este evaluată de fiecare dată (de exemplumaxim!(citeste(), 5)ar citi de două ori). Salvează-o întâi într-o variabilă (let a = $x;), ca în exemplul 1.
Comenzi înrudite
vec!: Creează un Vec cu elementele date, sau cu n copii ale aceleiași valori, într-o singură expresie.println! și print!: Afișează text pe ieșirea standard: println! adaugă la final un rând nou, print! nu. Acoladele {} sunt înlocuite cu valori.atribute (#[derive], #[cfg], #[allow]): Informații pentru compilator scrise cu #[…] deasupra unui element: generează cod (derive), aleg codul compilat (cfg) sau schimbă avertismentele.assert_eq! și assert_ne!: Verifică dacă două valori sunt egale (assert_eq!) sau diferite (assert_ne!); la eșec, panica afișează ambele valori.