수색…


비고

매크로의 연습은 The Rust Programming Language (The Book) 에서 찾을 수 있습니다.

지도 시간

매크로를 사용하면 여러 번 반복되는 구문 패턴을 추상화 할 수 있습니다. 예를 들면 :

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

두 개의 match 문은 매우 유사합니다. 두 문 모두 동일한 패턴을가집니다.

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

위의 패턴을 try_opt!(expression) 로 표현한다고 가정하면 함수를 단 3 줄로 다시 작성할 수 있습니다.

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! 함수가 조기 반환을 지원하지 않기 때문에 함수를 쓸 수 없습니다. 그러나 우리는 매크로를 사용하여이를 수행 할 수 있습니다. 함수를 사용하여 표현할 수없는 이러한 구문 패턴이있을 때마다 매크로를 사용하려고 시도 할 수 있습니다.

우리는 macro_rules! 사용하여 매크로를 정의합니다 macro_rules! 통사론:

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

그게 다야! 첫 번째 매크로를 만들었습니다.

( Rust Playground 에서 시도해보십시오.)

HashSet 매크로 만들기

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

재귀

매크로는 함수 재귀처럼 자체를 호출 할 수 있습니다.

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

sum!(1, 2, 3) 의 확장을 sum!(1, 2, 3) :

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

재귀 제한

컴파일러가 매크로를 너무 깊게 확장하면 포기하게됩니다. 기본적으로 컴파일러는 매크로를 64 레벨 깊이로 확장 한 후에 실패합니다. 예를 들어 다음과 같은 확장으로 인해 실패가 발생합니다.

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

재귀 한계에 도달하면 매크로 리팩터링을 고려해야합니다.

  • 어쩌면 재귀가 반복으로 대체 될 수 있습니까?
  • 어쩌면 입력 형식을 덜 공상적인 것으로 변경할 수 있으므로 일치시키기 위해 재귀가 필요하지 않습니까?

64 레벨로는 충분하지 않은 정당한 이유가있는 경우 다음 속성을 사용하여 매크로를 호출하는 크레이트의 한계를 늘릴 수 있습니다.

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

여러 패턴

매크로는 서로 다른 입력 패턴에 대해 서로 다른 출력을 생성 할 수 있습니다.

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

단편 지정자 - 패턴의 종류

$e:expr 에서 exprfragment 지정 expr 불린다. $e 매개 변수가 어떤 종류의 토큰인지를 파서에게 알려줍니다. Rust는 다양한 조각 지정자를 제공하므로 입력을 매우 유연하게 할 수 있습니다.

지정자 기술 예제들
ident 식별자 x , foo
path 공인 된 이름 std::collection::HashSet , Vec::new
ty 유형 i32 , &T , Vec<(char, String)>
expr 표현 2+2 , f(42) , if true { 1 } else { 2 }
pat 무늬 _ , c @ 'a' ... 'z' , (true, &x) , Badger { age, .. }
stmt 성명서 let x = 3 , return 42
block 가새 구분 블록 { foo(); bar(); } , { x(); y(); z() }
item fn foo() {} , struct Bar; , use std::io;
meta 속성 내부 cfg!(windows) , doc="comment"
tt 토큰 트리 + , foo , 5 , [?!(???)]

문서 주석 /// comment #[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;

팔로우

일부 프래그먼트 지정자는 "follow set"이라고하는 제한된 세트 중 하나 여야합니다. 이렇게하면 기존 매크로를 손상시키지 않고 Rust의 구문을 발전시킬 수 있습니다.

지정자 팔로우
expr , stmt => , ;
ty , path => , = | ; : > [ { as where
pat => , = | if in
ident , block , item , meta , tt 어떤 토큰
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_export]
//  ^~~~~~~~~~~~~~~ Think of it as `pub` for macros.
    macro_rules! my_macro { (..) => {..} }

다른 상자 또는 모듈의 매크로 사용 :

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

매크로 디버깅

(이들 모두는 불안정하므로 야간 컴파일러에서만 사용할 수 있습니다.)

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

컴파일하는 동안 stdout에 다음을 출력합니다 :

a = 1, rest = 2, 3
a = 2, 휴식 = 3
밑 = 3

--pretty expanded

다음과 같이 컴파일러를 실행하십시오.

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

이렇게하면 모든 매크로가 확장되어 확장 된 결과가 stdout으로 출력됩니다. 예를 들어 위의 내용이 출력됩니다 :

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

(이것은 C 컴파일러 gccclang-E 플래그와 유사합니다.)



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow