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



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow