Recherche…


Remarques

Une revue des macros peut être trouvée dans le langage de programmation Rust (aka The Book) .

Didacticiel

Les macros nous permettent d'abstraire des schémas syntaxiques répétés plusieurs fois. Par exemple:

/// Computes `a + b * c`. If any of the operation overflows, returns `None`.
fn checked_fma(a: u64, b: u64, c: u64) -> Option<u64> {
    let product = match b.checked_mul(c) {
        Some(p) => p,
        None => return None,
    };
    let sum = match a.checked_add(product) {
        Some(s) => s,
        None => return None,
    };
    Some(sum)
}

Nous remarquons que les deux déclarations de match sont très similaires: les deux ont le même motif

match expression {
    Some(x) => x,
    None => return None,
}

Imaginez que nous représentions le modèle ci-dessus en tant que try_opt!(expression) , nous pourrions alors réécrire la fonction en 3 lignes seulement:

fn checked_fma(a: u64, b: u64, c: u64) -> Option<u64> {
    let product = try_opt!(b.checked_mul(c));
    let sum = try_opt!(a.checked_add(product));
    Some(sum)
}

try_opt! ne peut pas écrire une fonction car une fonction ne prend pas en charge le retour anticipé. Mais nous pourrions le faire avec une macro - chaque fois que nous avons ces modèles syntaxiques qui ne peuvent pas être représentés en utilisant une fonction, nous pouvons essayer d'utiliser une macro.

Nous définissons une macro en utilisant la macro_rules! syntaxe:

macro_rules! try_opt {
//                  ^ note: no `!` after the macro name
    ($e:expr) => {
//   ^~~~~~~ The macro accepts an "expression" argument, which we call `$e`.
//           All macro parameters must be named like `$xxxxx`, to distinguish from
//           normal tokens.
        match $e {
//            ^~ The input is used here.
            Some(x) => x,
            None => return None,
        }
    }
}

C'est tout! Nous avons créé notre première macro.

(Essayez-le dans Rust Playground )

Créer une macro HashSet

// This example creates a macro `set!` that functions similarly to the built-in
// macro vec!

use std::collections::HashSet;

macro_rules! set {
    ( $( $x:expr ),* ) => {  // Match zero or more comma delimited items
        {
            let mut temp_set = HashSet::new();  // Create a mutable HashSet
            $(
                temp_set.insert($x); // Insert each item matched into the HashSet
            )*
            temp_set // Return the populated HashSet
        }
    };
}

// Usage
let my_set = set![1, 2, 3, 4];

Récursivité

Une macro peut s'appeler, comme une récursion de fonction:

macro_rules! sum {
    ($base:expr) => { $base };
    ($a:expr, $($rest:expr),+) => { $a + sum!($($rest),+) };
}

Allons-y l'expansion de la sum!(1, 2, 3) :

   sum!(1, 2, 3)
//      ^  ^~~~
//     $a  $rest
=> 1 + sum!(2, 3)
//          ^  ^
//         $a  $rest
=> 1 + (2 + sum!(3))
//               ^
//               $base
=> 1 + (2 + (3))

Limite de récursivité

Lorsque le compilateur étend trop les macros, il va abandonner. Par défaut, le compilateur échouera après avoir étendu les macros à 64 niveaux, de sorte que l'extension suivante provoquera une défaillance:

sum!(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
     21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
     41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62)

// error: recursion limit reached while expanding the macro `sum`
//  --> <anon>:3:46
// 3 |>         ($a:expr, $($rest:expr),+) => { $a + sum!($($rest),+) };
//   |>                                              ^^^^^^^^^^^^^^^^

Lorsqu'une limite de récursivité est atteinte, vous devriez envisager de refactoriser votre macro, par exemple

  • Peut-être la récursivité pourrait-elle être remplacée par la répétition?
  • Peut-être que le format d'entrée pourrait être changé en quelque chose de moins sophistiqué, donc nous n'avons pas besoin de récursivité pour le faire correspondre?

S'il y a une raison légitime, 64 niveaux ne suffisent pas, vous pouvez toujours augmenter la limite de la caisse en appelant la macro avec l'attribut:

#![recursion_limit="128"]
//                  ^~~ set the recursion limit to 128 levels deep.

Plusieurs modèles

Une macro peut produire différentes sorties par rapport à différents modèles d'entrée:

/// The `sum` macro may be invoked in two ways:
/// 
///     sum!(iterator)
///     sum!(1234, iterator)
///
macro_rules! sum {
    ($iter:expr) => {    // This branch handles the `sum!(iterator)` case
        $iter.fold(0, |a, b| a + *b)
    };
//   ^ use `;` to separate each branch
    ($start:expr, $iter:expr) => {  // This branch handles the `sum!(1234, iter)` case
        $iter.fold($start, |a, b| a + *b)
    };
}

fn main() {
    assert_eq!(10, sum!([1, 2, 3, 4].iter()));
    assert_eq!(23, sum!(6, [2, 5, 9, 1].iter()));
}

Spécificateurs de fragment - Type de modèle

Dans $e:expr , l' expr est appelé le spécificateur de fragment . Il indique à l'analyseur quel type de jeton le paramètre $e attend. Rust fournit une variété de spécificateurs de fragment, permettant à l'entrée d'être très flexible.

Spécificateur La description Exemples
ident Identifiant x , foo
path Nom qualifié std::collection::HashSet , Vec::new
ty Type i32 , &T , Vec<(char, String)>
expr Expression 2+2 , f(42) , if true { 1 } else { 2 }
pat Modèle _ , c @ 'a' ... 'z' , (true, &x) , Badger { age, .. }
stmt Déclaration let x = 3 , return 42
block Bloc délimité par des accolades { foo(); bar(); } , { x(); y(); z() }
item Article fn foo() {} , struct Bar; , use std::io;
meta Intérieur de l'attribut cfg!(windows) , doc="comment"
tt Arbre jeton + , foo , 5 , [?!(???)]

Notez qu'un commentaire de commentaire /// comment est traité de la même manière que #[doc="comment"] dans une macro.

macro_rules! declare_const_option_type {
    (
        $(#[$attr:meta])*
        const $name:ident: $ty:ty as optional;
    ) => {
        $(#[$attr])*
        const $name: Option<$ty> = None;
    }
}

declare_const_option_type! {
    /// some doc comment
    const OPT_INT: i32 as optional;
}

// The above will be expanded to:
#[doc="some doc comment"]
const OPT_INT: Option<i32> = None;

Suivre ensemble

Certains spécificateurs de fragment nécessitent que le jeton suivant soit un ensemble restreint, appelé "ensemble de suivi". Cela permet une certaine souplesse pour que la syntaxe de Rust évolue sans casser les macros existantes.

Spécificateur Suivre ensemble
expr , stmt => , ;
ty , path => , = | ; : > [ { as where
pat => , = | if in
ident , block , item , meta , tt n'importe quel jeton
macro_rules! invalid_macro {
    ($e:expr + $f:expr) => { $e + $f };
//           ^
//           `+` is not in the follow set of `expr`,
//           and thus the compiler will not accept this macro definition.
    ($($e:expr)/+) => { $($e)/+ };
//             ^
//             The separator `/` is not in the follow set of `expr`
//             and thus the compiler will not accept this macro definition.
}

Exportation et importation de macros

Exportation d'une macro pour permettre à d'autres modules de l'utiliser:

    #[macro_export]
//  ^~~~~~~~~~~~~~~ Think of it as `pub` for macros.
    macro_rules! my_macro { (..) => {..} }

Utiliser des macros d'autres caisses ou modules:

   #[macro_use] extern crate lazy_static;
// ^~~~~~~~~~~~ Must add this in order to use macros from other crates 

   #[macro_use] mod macros;
// ^~~~~~~~~~~~ The same for child modules.

Débogage des macros

(Tous ces éléments sont instables et ne peuvent donc être utilisés que par un compilateur nocturne.)

log_syntax! ()

#![feature(log_syntax)]

macro_rules! logged_sum {
    ($base:expr) => { 
        { log_syntax!(base = $base); $base } 
    };
    ($a:expr, $($rest:expr),+) => { 
        { log_syntax!(a = $a, rest = $($rest),+); $a + logged_sum!($($rest),+) } 
    };
}

const V: u32 = logged_sum!(1, 2, 3);

Lors de la compilation, il affichera ce qui suit dans stdout:

a = 1, reste = 2, 3
a = 2, reste = 3
base = 3

--pretty élargi

Exécutez le compilateur avec:

rustc -Z unstable-options --pretty expanded filename.rs

Cela va développer toutes les macros et ensuite imprimer le résultat étendu à stdout, par exemple, le résultat ci-dessus va probablement sortir:

#![feature(prelude_import)]
#![no_std]
#![feature(log_syntax)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;


const V: u32 = { false; 1 + { false; 2 + { false; 3 } } };

(Ceci est similaire à la -E drapeau dans les compilateurs C gcc et clang ).



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow