Zoeken…


Opmerkingen

Een doorloop van macro's is te vinden in The Rust Programming Language (ook bekend als The Book) .

Tutorial

Macro's stellen ons in staat om syntactische patronen te abstraheren die vele malen worden herhaald. Bijvoorbeeld:

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

We merken dat de twee match statements sterk op elkaar lijken: beide hebben hetzelfde patroon

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

Stel je voor dat we het bovenstaande patroon voorstellen als try_opt!(expression) , dan kunnen we de functie in slechts 3 regels herschrijven:

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 geen functie schrijven omdat een functie de vroege terugkeer niet ondersteunt. Maar we zouden het met een macro kunnen doen - wanneer we deze syntactische patronen hebben die niet kunnen worden weergegeven met behulp van een functie, kunnen we proberen een macro te gebruiken.

We definiëren een macro met behulp van de macro_rules! syntaxis:

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

Dat is het! We hebben onze eerste macro gemaakt.

(Probeer het in Rust Playground )

Maak een HashSet-macro

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

Herhaling

Een macro kan zichzelf noemen, zoals een functie-recursie:

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

Laten we gaan door de uitbreiding van de sum!(1, 2, 3) :

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

Recursielimiet

Wanneer de compiler macro's te diep uitbreidt, geeft hij het op. Standaard mislukt de compiler na het uitbreiden van macro's tot 64 niveaus diep, dus bijvoorbeeld de volgende uitbreiding zal mislukken:

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

Wanneer een recursielimiet is bereikt, moet u overwegen uw macro opnieuw te factureren, bijvoorbeeld

  • Misschien kan recursie worden vervangen door herhaling?
  • Misschien kan het invoerformaat worden gewijzigd in iets minder bijzonders, zodat we geen recursie nodig hebben om het te matchen?

Als er een legitieme reden is dat 64 niveaus niet genoeg zijn, kunt u de limiet van de krat die de macro aanroept altijd verhogen met het kenmerk:

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

Meerdere patronen

Een macro kan verschillende uitvoer produceren tegen verschillende invoerpatronen:

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

Fragmentspecificaties - Soort patronen

In $e:expr , wordt de expr de fragment-specificeerder genoemd . Het vertelt de parser wat voor soort tokens de parameter $e verwacht. Rust biedt een verscheidenheid aan fragmentspecificaties, waardoor de invoer zeer flexibel is.

specifier Beschrijving Voorbeelden
ident Identifier x , foo
path Gekwalificeerde naam std::collection::HashSet , Vec::new
ty Type i32 , &T , Vec<(char, String)>
expr Uitdrukking 2+2 , f(42) , if true { 1 } else { 2 }
pat Patroon _ , c @ 'a' ... 'z' , (true, &x) , Badger { age, .. }
stmt Uitspraak let x = 3 , return 42
block Brace-gescheiden blok { foo(); bar(); } , { x(); y(); z() }
item Item fn foo() {} , struct Bar; , use std::io;
meta Binnenkant van kenmerk cfg!(windows) , doc="comment"
tt Tokenboom + , foo , 5 , [?!(???)]

Merk op dat een doc-opmerking /// comment hetzelfde wordt behandeld als #[doc="comment"] voor een macro.

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;

Volg set

Sommige fragment-specificatie vereist het token dat volgt, het moet een van een beperkte set zijn, de "volgset" genoemd. Dit biedt enige flexibiliteit voor de syntaxis van Rust om te evolueren zonder bestaande macro's te verbreken.

specifier Volg set
expr , stmt => , ;
ty , path => , = | ; : > [ { as where
pat => , = | if in
ident , block , item , meta , tt elk 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.
}

Macro's exporteren en importeren

Een macro exporteren zodat andere modules deze kunnen gebruiken:

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

Macro's van andere kratten of modules gebruiken:

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

Foutopsporing macro's

(Al deze zijn onstabiel en kunnen dus alleen worden gebruikt vanuit een nachtelijke compiler.)

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

Tijdens het compileren wordt het volgende naar stdout afgedrukt:

a = 1, rust = 2, 3
a = 2, rust = 3
basis = 3

--vrij uitgebreid

Voer de compiler uit met:

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

Hiermee worden alle macro's uitgevouwen en wordt het uitgevouwen resultaat vervolgens afgedrukt naar stdout, het bovenstaande zal waarschijnlijk het volgende uitvoeren:

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

(Dit is vergelijkbaar met de vlag -E in de C-compilers gcc en clang .)



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow