Suche…


Bemerkungen

Eine ausführliche Anleitung zu Makros finden Sie in der Programmiersprache Rust (aka The Book) .

Tutorial

Mithilfe von Makros können wir syntaktische Muster abstrahieren, die sich oft wiederholen. Zum Beispiel:

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

Wir stellen fest, dass die beiden match sehr ähnlich sind: Beide haben dasselbe Muster

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

Stellen Sie sich vor, wir stellen das obige Muster als try_opt!(expression) , dann könnten wir die Funktion in nur 3 Zeilen umschreiben:

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! Eine Funktion kann nicht geschrieben werden, da eine Funktion die vorzeitige Rückkehr nicht unterstützt. Aber wir könnten es mit einem Makro machen. Immer wenn wir diese syntaktischen Muster haben, die nicht mit einer Funktion dargestellt werden können, versuchen wir, ein Makro zu verwenden.

Wir definieren ein Makro mit den macro_rules! Syntax:

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

Das ist es! Wir haben unser erstes Makro erstellt.

(Versuchen Sie es in Rust Playground )

Erstellen Sie ein HashSet-Makro

// 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];

Rekursion

Ein Makro kann sich wie eine Funktionsrekursion selbst aufrufen:

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

Gehen wir durch die Erweiterung der sum!(1, 2, 3) :

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

Rekursionslimit

Wenn der Compiler die Makros zu stark erweitert, gibt er auf. Standardmäßig schlägt der Compiler fehl, nachdem die Makros auf 64 Ebenen erweitert wurden. Die folgende Erweiterung führt beispielsweise zu einem Fehler:

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

Wenn ein Rekursionslimit erreicht ist, sollten Sie Ihr Makro umgestalten, z

  • Vielleicht könnte Rekursion durch Wiederholung ersetzt werden?
  • Möglicherweise kann das Eingabeformat in etwas weniger Fancy geändert werden, sodass wir keine Rekursion benötigen, um es anzupassen.

Wenn es einen legitimen Grund gibt, dass 64 Ebenen nicht ausreichen, können Sie das Limit der Kiste, die das Makro aufruft, mit dem Attribut erhöhen:

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

Mehrere Muster

Ein Makro kann unterschiedliche Ausgaben für unterschiedliche Eingabemuster erzeugen:

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

Fragmentspezifizierer - Art der Muster

In $e:expr wird der expr als Fragmentbezeichner bezeichnet . Sie teilt dem Parser mit, welche Art von Token der Parameter $e erwartet. Rust bietet eine Vielzahl von Fragmentspezifizierern an, so dass die Eingabe sehr flexibel ist.

Bezeichner Beschreibung Beispiele
ident Kennung x , foo
path Qualifizierter Name std::collection::HashSet , Vec::new
ty Art i32 , &T , Vec<(char, String)>
expr Ausdruck 2+2 , f(42) , if true { 1 } else { 2 }
pat Muster _ , c @ 'a' ... 'z' ( (true, &x) , Badger { age, .. }
stmt Aussage let x = 3 , return 42
block Von der Klammer getrennter Block { foo(); bar(); } , { x(); y(); z() }
item Artikel fn foo() {} , struct Bar; use std::io;
meta Innerhalb des Attributs cfg!(windows) , doc="comment"
tt Token-Baum + , foo , 5 , [?!(???)]

Beachten Sie, dass ein Dokumentkommentar /// comment einem Makro gleich behandelt wird wie #[doc="comment"] .

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;

Set folgen

Einige Fragmentspezifizierer erfordern, dass das Token einem eingeschränkten Satz folgt, der als Folgesatz bezeichnet wird. Dies ermöglicht eine gewisse Flexibilität für die Entwicklung der Rust-Syntax, ohne dass vorhandene Makros beschädigt werden.

Bezeichner Set folgen
expr , stmt => , ;
ty path => , = | ; : > [ { as where
pat => , = | if in
ident , block , item , meta , tt irgendein Zeichen
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.
}

Makros exportieren und importieren

Exportieren eines Makros, damit andere Module es verwenden können:

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

Verwendung von Makros aus anderen Kisten oder Modulen:

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

Debuggen von Makros

(Alle sind instabil und können daher nur von einem nächtlichen Compiler verwendet werden.)

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

Während des Kompilierens wird folgendes an stdout gedruckt:

a = 1, Rest = 2, 3
a = 2, Rest = 3
Basis = 3

- pretty erweitert

Führen Sie den Compiler aus mit:

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

Dadurch werden alle Makros erweitert, und das erweiterte Ergebnis wird dann in stdout ausgegeben.

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

(Dies ist ähnlich der -E Flag in den C-Compilern gcc und clang .)



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow