Buscar..


Observaciones

Puede encontrar un tutorial de macros en The Rust Programming Language (también conocido como The Book) .

Tutorial

Las macros nos permiten abstraer patrones sintácticos que se repiten muchas veces. Por ejemplo:

/// 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)
}

Notamos que las dos declaraciones de match son muy similares: ambas tienen el mismo patrón

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

Imagina que representamos el patrón anterior como try_opt!(expression) , luego podríamos reescribir la función en solo 3 líneas:

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! no se puede escribir una función porque una función no admite el retorno anticipado. Pero podríamos hacerlo con una macro: siempre que tengamos estos patrones sintácticos que no se pueden representar usando una función, podemos tratar de usar una macro.

Definimos una macro usando las macro_rules! sintaxis:

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,
        }
    }
}

¡Eso es! Hemos creado nuestra primera macro.

(Pruébalo en Rust Playground )

Crear una 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];

Recursion

Una macro puede llamarse a sí misma, como una función recursiva:

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

¡Vamos por la expansión 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))

Límite de recursión

Cuando el compilador está expandiendo las macros demasiado profundamente, se rendirá. De forma predeterminada, el compilador fallará después de expandir las macros a 64 niveles de profundidad, por lo que, por ejemplo, la siguiente expansión causará un error:

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),+) };
//   |>                                              ^^^^^^^^^^^^^^^^

Cuando se alcanza un límite de recursión, debería considerar refactorizar su macro, por ejemplo,

  • ¿Tal vez la recursión podría ser reemplazada por la repetición?
  • Tal vez el formato de entrada podría cambiarse a algo menos sofisticado, por lo que no necesitamos recursión para que coincida.

Si hay alguna razón legítima para que 64 niveles no sean suficientes, siempre puede aumentar el límite de la caja invocando la macro con el atributo:

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

Patrones multiples

Una macro puede producir diferentes salidas contra diferentes patrones de entrada:

/// 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()));
}

Especificadores de fragmentos - Tipo de patrones

En $e:expr , el expr se llama el especificador de fragmento . Le dice al analizador qué tipo de tokens está esperando el parámetro $e . Rust proporciona una variedad de especificadores de fragmentos para permitir que la entrada sea muy flexible.

Especificador Descripción Ejemplos
ident Identificador x , foo
path Nombre calificado std::collection::HashSet , Vec::new
ty Tipo i32 , &T , Vec<(char, String)>
expr Expresión 2+2 , f(42) , if true { 1 } else { 2 }
pat Modelo _ , c @ 'a' ... 'z' , (true, &x) , Badger { age, .. }
stmt Declaración let x = 3 , return 42
block Bloque delimitado por llaves { foo(); bar(); } , { x(); y(); z() }
item ít fn foo() {} , struct Bar; , use std::io;
meta Dentro del atributo cfg!(windows) , doc="comment"
tt Árbol simbólico + , foo , 5 , [?!(???)]

Tenga en cuenta que un comentario doc /// comment se trata de la misma forma que #[doc="comment"] a una 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;

Seguir set

Algunos especificadores de fragmentos requieren que el token que sigue debe ser uno de un conjunto restringido, denominado "conjunto de seguimiento". Esto permite cierta flexibilidad para que la sintaxis de Rust evolucione sin romper las macros existentes.

Especificador Seguir set
expr , stmt => , ;
ty , path => , = | ; : > [ { as where
pat => , = | if in
ident , block , item , meta , tt cualquier token
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.
}

Exportando e importando macros

Exportando una macro para permitir que otros módulos la utilicen:

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

Usando macros de otras cajas o módulos:

   #[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.

Depuración de macros

(Todos estos son inestables y, por lo tanto, solo pueden usarse desde un compilador nocturno).

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);

Durante la compilación, imprimirá lo siguiente en la salida estándar:

a = 1, resto = 2, 3
a = 2, resto = 3
base = 3

--muy expandido

Ejecuta el compilador con:

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

Esto expandirá todas las macros y luego imprimirá el resultado expandido en la salida estándar, por ejemplo, lo anterior probablemente generará:

#![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 } } };

(Esto es similar a la bandera -E en los compiladores de C gcc y clang .)



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow