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