Szukaj…


Uwagi

Omówienie makr można znaleźć w The Rust Programming Language (alias The Book) .

Instruktaż

Makra pozwalają nam abstrakcyjnie powtarzać wzorce syntaktyczne. Na przykład:

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

Zauważamy, że dwie instrukcje match są bardzo podobne: obie mają ten sam wzór

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

Wyobraźmy sobie, że reprezentujemy powyższy wzorzec jako try_opt!(expression) , a następnie moglibyśmy przepisać funkcję na 3 linie:

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! nie można napisać funkcji, ponieważ funkcja nie obsługuje wcześniejszego powrotu. Ale możemy to zrobić za pomocą makra - ilekroć mamy te wzorce składniowe, których nie można przedstawić za pomocą funkcji, możemy spróbować użyć makra.

Definiujemy makro za pomocą macro_rules! składnia:

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

Otóż to! Stworzyliśmy nasze pierwsze makro.

(Wypróbuj w Rust Playground )

Utwórz makro 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];

Rekurencja

Makro może się wywoływać, podobnie jak rekurencja funkcji:

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

Przejdźmy jednak do rozszerzenia sum!(1, 2, 3) :

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

Limit rekurencji

Gdy kompilator zbyt głęboko rozwija makra, zrezygnuje. Domyślnie kompilator zawiedzie po rozszerzeniu makr do 64 poziomów głębokości, więc np. Następujące rozszerzenie spowoduje awarię:

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

Po osiągnięciu limitu rekurencji należy rozważyć refaktoryzację makra, np

  • Może rekurencję można zastąpić powtórzeniem?
  • Może format wejściowy może zostać zmieniony na mniej wyszukany, więc nie potrzebujemy rekurencji, aby go dopasować?

Jeśli istnieje uzasadniony powód, dla którego poziomy 64 nie są wystarczające, zawsze możesz zwiększyć limit skrzynki wywołującej makro za pomocą atrybutu:

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

Wiele wzorów

Makro może wytwarzać różne dane wyjściowe dla różnych wzorców wejściowych:

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

Specyfikatory fragmentów - rodzaj wzorów

W $e:expr expr nazywa się specyfikatorem fragmentu . Mówi parserowi, jakiego rodzaju tokenów oczekuje parametr $e . Rdza zapewnia różnorodne specyfikatory fragmentów, dzięki czemu dane wejściowe są bardzo elastyczne.

Specyficzny Opis Przykłady
ident Identyfikator x , foo
path Imię i nazwisko std::collection::HashSet , Vec::new
ty Rodzaj i32 , &T , Vec<(char, String)>
expr Wyrażenie 2+2 , f(42) , if true { 1 } else { 2 }
pat Wzór _ , c @ 'a' ... 'z' , (true, &x) , Badger { age, .. }
stmt Komunikat let x = 3 , return 42
block Blok rozdzielany nawiasami klamrowymi { foo(); bar(); } , { x(); y(); z() }
item Pozycja fn foo() {} , struct Bar; , use std::io;
meta Wewnątrz atrybutu cfg!(windows) , doc="comment"
tt Drzewo żetonów + , foo , 5 , [?!(???)]

Zauważ, że komentarz do dokumentu /// comment jest traktowany tak samo jak #[doc="comment"] do makra.

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;

Śledź zestaw

Niektóre specyfikatory fragmentów wymagają, aby następujący token był jednym z ograniczonego zestawu, zwanego „zestawem śledzenia”. Pozwala to na pewną elastyczność w rozwijaniu składni Rust bez przerywania istniejących makr.

Specyficzny Śledź zestaw
expr , stmt => , ;
ty , path => , = | ; : > [ { as where
pat => , = | if in
ident , block , item , meta , tt dowolny 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.
}

Eksportowanie i importowanie makr

Eksportowanie makra, aby umożliwić innym modułom korzystanie z niego:

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

Używanie makr z innych skrzynek lub modułów:

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

Debugowanie makr

(Wszystkie z nich są niestabilne i dlatego można ich używać tylko z nocnego kompilatora).

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

Podczas kompilacji wypisze na standardowe wyjście:

a = 1, reszta = 2, 3
a = 2, reszta = 3
podstawa = 3

- ładny rozszerzony

Uruchom kompilator za pomocą:

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

Spowoduje to rozwinięcie wszystkich makr, a następnie wydrukuje rozwinięty wynik na standardowe wyjście, np. Powyższe prawdopodobnie wygeneruje:

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

(Jest to podobne do flagi -E w kompilatorach C gcc i clang .)



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow