13. Macros

Les macros en Rust sont du code qui génère du code. Il existe deux types : les macros déclaratives (macro_rules!) pour le pattern matching sur le code source, et les macros procédurales (#[derive], #[tokio::main]) pour des transformations plus puissantes.


13.1 Macro Rules (macro_rules!)

Déclaration

macro_rules! nom_de_la_macro {
    ($arg:expr) => {
        // code généré
    };
}

Exemples utiles

// Macro simple : log avec niveau
macro_rules! log_gradient {
    ($level:expr, $msg:expr) => {
        println!("[{}] {}", $level, $msg);
    };
}
 
log_gradient!("INFO", "gradient reçu");  // [INFO] gradient reçu
 
// Macro avec plusieurs arguments
macro_rules! create_gar {
    ($name:ident, $point_rupture:expr) => {
        struct $name;
        impl $name {
            fn breakdown_point(&self) -> f64 { $point_rupture }
        }
    };
}
 
create_gar!(Median, 0.5);
create_gar!(Krum, 0.5);
 
let m = Median;
println!("{}", m.breakdown_point());  // 0.5

Macro avec répétition

macro_rules! gradient_matrix {
    ( $( $row:expr ),+ ) => {
        vec![
            $( vec![$row] ),+
        ]
    };
}
 
let grads = gradient_matrix!(
    [1.0, 2.0, 3.0],
    [4.0, 5.0, 6.0],
    [7.0, 8.0, 9.0]
);
// = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0], vec![7.0, 8.0, 9.0]]

Pattern matching dans les macros

macro_rules! compute_median {
    // Cas : liste de valeurs
    ($($val:expr),+) => {
        {
            let mut v = vec![$($val),+];
            v.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
            v[v.len() / 2]
        }
    };
    // Cas : vecteur
    ($vec:expr) => {
        {
            let mut v = $vec;
            v.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
            v[v.len() / 2]
        }
    };
}
 
let m1 = compute_median!(3, 1, 4, 1, 5);  // 3
let v = vec![9.0, 1.0, 5.0];
let m2 = compute_median!(v);  // 5.0

13.2 Macros de la bibliothèque standard

Les plus utilisées :

// vec! — la plus familière
let v = vec![1, 2, 3];
 
// println! / format!
println!("valeur: {}", x);
let s = format!("{:.2}", pi);
 
// assert!
assert_eq!(x, 42);
assert!(x > 0, "x doit être positif");
 
// todo! / unimplemented!
fn future_feature() {
    todo!("implémenter plus tard");
}
 
// matches!
if matches!(status, Status::Active | Status::Pending) {
    println!("en cours");
}
 
// cfg! (compilation conditionnelle)
if cfg!(target_os = "linux") {
    println!("spécifique Linux");
}
 
// include_str! / include_bytes!
const CONFIG: &str = include_str!("config.toml");
const MODEL_HEADER: &[u8] = include_bytes!("model_header.bin");

13.3 Macros Procédurales

Les macros procédurales sont du code Rust qui s’exécute à la compilation pour transformer le code source.

Derive Macro (#[derive(Trait)])

use serde::{Serialize, Deserialize};
 
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GradientConfig {
    dimension: usize,
    n_workers: usize,
}
// Sérialisation et désérialisation automatiques

Créer sa propre derive macro (avancé)

// Dans un crate séparé : gradient-derive/Cargo.toml
// [lib]
// proc-macro = true
 
// gradient-derive/src/lib.rs
use proc_macro::TokenStream;
 
#[proc_macro_derive(GarInfo)]
pub fn gar_info_derive(input: TokenStream) -> TokenStream {
    // Parser le struct, ajouter une méthode broken_point()
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
 
    let expanded = quote! {
        impl #name {
            pub fn breakdown_point(&self) -> f64 {
                0.5  // exemple
            }
        }
    };
    expanded.into()
}

13.4 Exemples Concrets

Macro pour construire une config

macro_rules! config {
    ($($key:ident: $value:expr),* $(,)?) => {
        {
            let mut c = ExperimentConfig::default();
            $( c.$key = $value; )*
            c
        }
    };
}
 
let cfg = config! {
    learning_rate: 0.001,
    gar: "median".to_string(),
    n_workers: 50,
    f: 10,
};

Macro pour mesurer le temps

macro_rules! time {
    ($name:expr, $code:expr) => {
        {
            let start = std::time::Instant::now();
            let result = $code;
            let elapsed = start.elapsed();
            println!("[TIMING] {}: {:?}", $name, elapsed);
            result
        }
    };
}
 
let result = time!("krum 50×10k", {
    let krum = Krum::new(10);
    krum.aggregate(&large_grads).unwrap()
});

13.5 Résumé

Type de macroSyntaxeUsage
Déclarativemacro_rules!Code répétitif, DSL simples
Derive#[derive(Trait)]Implémentation automatique de traits
Attribute#[tokio::main]Transformation de fonctions/modules
Function-likemy_macro!()Comme une fonction mais sur le code source

Règle : quand un pattern se répète, faites-en une macro. Mais préférez une fonction quand c’est possible.


🔗 Voir aussi