Factoriser la répétition avec les macros Rust

Beginner

This tutorial is from open-source community. Access the source code

Introduction

Dans ce laboratoire, nous explorons le concept de DRY (Ne te répète pas) dans la programmation en utilisant des macros pour extraire les parties communes des fonctions et des suites de tests.

Note : Si le laboratoire ne spécifie pas de nom de fichier, vous pouvez utiliser n'importe quel nom de fichier que vous voulez. Par exemple, vous pouvez utiliser main.rs, le compiler et l'exécuter avec rustc main.rs &&./main.

DRY (Ne te répète pas)

Les macros permettent d'écrire du code DRY en extraisant les parties communes des fonctions et/ou des suites de tests. Voici un exemple qui implémente et teste les opérateurs +=, *= et -= sur Vec<T> :

use std::ops::{Add, Mul, Sub};

macro_rules! assert_equal_len {
    // Le désignateur `tt` (arbre de jetons) est utilisé pour
    // les opérateurs et les jetons.
    ($a:expr, $b:expr, $func:ident, $op:tt) => {
        assert!($a.len() == $b.len(),
                "{:?}: mismatch de dimension: {:?} {:?} {:?}",
                stringify!($func),
                ($a.len(),),
                stringify!($op),
                ($b.len(),));
    };
}

macro_rules! op {
    ($func:ident, $bound:ident, $op:tt, $method:ident) => {
        fn $func<T: $bound<T, Output=T> + Copy>(xs: &mut Vec<T>, ys: &Vec<T>) {
            assert_equal_len!(xs, ys, $func, $op);

            for (x, y) in xs.iter_mut().zip(ys.iter()) {
                *x = $bound::$method(*x, *y);
                // *x = x.$method(*y);
            }
        }
    };
}

// Implémente les fonctions `add_assign`, `mul_assign` et `sub_assign`.
op!(add_assign, Add, +=, add);
op!(mul_assign, Mul, *=, mul);
op!(sub_assign, Sub, -=, sub);

mod test {
    use std::iter;
    macro_rules! test {
        ($func:ident, $x:expr, $y:expr, $z:expr) => {
            #[test]
            fn $func() {
                for size in 0usize..10 {
                    let mut x: Vec<_> = iter::repeat($x).take(size).collect();
                    let y: Vec<_> = iter::repeat($y).take(size).collect();
                    let z: Vec<_> = iter::repeat($z).take(size).collect();

                    super::$func(&mut x, &y);

                    assert_eq!(x, z);
                }
            }
        };
    }

    // Teste `add_assign`, `mul_assign` et `sub_assign`.
    test!(add_assign, 1u32, 2u32, 3u32);
    test!(mul_assign, 2u32, 3u32, 6u32);
    test!(sub_assign, 3u32, 2u32, 1u32);
}
$ rustc --test dry.rs && ./dry
running 3 tests
test test::mul_assign... ok
test test::add_assign... ok
test test::sub_assign... ok

test result: ok. 3 passed
0 failed
0 ignored
0 measured

Résumé

Félicitations! Vous avez terminé le laboratoire DRY (Ne te répète pas). Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.