수색…
비고
매크로의 연습은 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
에서 expr
은 fragment 지정 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 컴파일러 gcc
와 clang
의 -E
플래그와 유사합니다.)