Sök…
Anmärkningar
En genomgång av makron finns i The Rust Programming Language (aka The Book) .
Handledning
Makron tillåter oss att abstrahera syntaktiska mönster som upprepas många gånger. Till exempel:
/// 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)
}
Vi märker att de två match
är väldigt lika: båda har samma mönster
match expression {
Some(x) => x,
None => return None,
}
Föreställ dig att vi representerar ovanstående mönster som try_opt!(expression)
, då kan vi skriva om funktionen till bara tre rader:
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!
kan inte skriva en funktion eftersom en funktion inte stöder den tidiga returen. Men vi kan göra det med ett makro - när vi har dessa syntaktiska mönster som inte kan representeras med en funktion, kan vi försöka använda ett makro.
Vi definierar ett makro med hjälp av 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,
}
}
}
Det är allt! Vi har skapat vår första makro.
(Prova på Rust Playground )
Skapa en 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
Ett makro kan kalla sig själv, som en funktionsrekursion:
macro_rules! sum {
($base:expr) => { $base };
($a:expr, $($rest:expr),+) => { $a + sum!($($rest),+) };
}
Låt oss gå genom att utvidga sum!(1, 2, 3)
:
sum!(1, 2, 3)
// ^ ^~~~
// $a $rest
=> 1 + sum!(2, 3)
// ^ ^
// $a $rest
=> 1 + (2 + sum!(3))
// ^
// $base
=> 1 + (2 + (3))
Rekursionsgräns
När kompilatorn expanderar makron för djupt kommer den att ge upp. Som standard kommer kompilatorn att misslyckas efter att makronen har expanderats till 64 nivåer djup, så t.ex. kommer följande expansion att orsaka fel:
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),+) };
// |> ^^^^^^^^^^^^^^^^
När en rekursionsgräns uppnås bör du överväga att återföra din makro, t.ex.
- Kanske rekursion kan ersättas av upprepning?
- Kanske kan inmatningsformatet ändras till något mindre snyggt, så vi behöver inte rekursion för att matcha det?
Om det finns något berättigat skäl att 64 nivåer inte räcker, kan du alltid öka gränsen för lådan som åberopar makro med attributet:
#![recursion_limit="128"]
// ^~~ set the recursion limit to 128 levels deep.
Flera mönster
Ett makro kan producera olika utgångar mot olika inmatningsmönster:
/// 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()));
}
Fragmentspecifikationer - Typ av mönster
I $e:expr
kallas expr
fragmentfrisieraren . Den berättar för tolkaren vilken typ av symboler parametern $e
förväntar sig. Rust tillhandahåller en mängd fragmentspecifikationer till, vilket gör att ingången kan vara mycket flexibel.
Specifier | Beskrivning | exempel |
---|---|---|
ident | Identifierare | x , foo |
path | Kvalificerat namn | std::collection::HashSet , Vec::new |
ty | Typ | i32 , &T , Vec<(char, String)> |
expr | Uttryck | 2+2 , f(42) , if true { 1 } else { 2 } |
pat | Mönster | _ , c @ 'a' ... 'z' , (true, &x) , Badger { age, .. } |
stmt | Påstående | let x = 3 , return 42 |
block | Brace-avgränsat block | { foo(); bar(); } , { x(); y(); z() } |
item | Artikel | fn foo() {} , struct Bar; , use std::io; |
meta | Insidan av attributet | cfg!(windows) , doc="comment" |
tt | Token träd | + , foo , 5 , [?!(???)] |
Observera att en doc-kommentar /// comment
behandlas på samma sätt som #[doc="comment"]
till ett makro.
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;
Följ set
Vissa fragmentspecifikationer kräver att symbolen följer att den måste vara en av en begränsad uppsättning, kallad "följuppsättningen". Detta tillåter viss flexibilitet för Rasts syntax att utvecklas utan att bryta befintliga makron.
Specifier | Följ set |
---|---|
expr , stmt | => , ; |
ty , path | => , = | ; : > [ { as where |
pat | => , = | if in |
ident , block , item , meta , tt | vilket symbol som helst |
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.
}
Exportera och importera makron
Exportera ett makro för att tillåta andra moduler att använda det:
#[macro_export]
// ^~~~~~~~~~~~~~~ Think of it as `pub` for macros.
macro_rules! my_macro { (..) => {..} }
Använda makron från andra lådor eller moduler:
#[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.
Felsökningsmakroer
(Alla dessa är instabila och kan därför endast användas från en nattlig kompilator.)
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);
Under sammanställningen kommer det att skriva ut följande till stdout:
a = 1, vila = 2, 3
a = 2, vila = 3
bas = 3
- ganska utvidgad
Kör kompilatorn med:
rustc -Z unstable-options --pretty expanded filename.rs
Detta utvidgar alla makron och skriver sedan ut det utvidgade resultatet till stdout, t.ex. kommer ovanstående antagligen att matas ut:
#![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 } } };
(Detta liknar -E
flaggan i C-kompilatorerna gcc
och clang
.)