サーチ…
備考
マクロのウォークスルーは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)
}
2つのmatch
ステートメントは非常によく似ていることがわかります。両方とも同じパターン
match expression {
Some(x) => x,
None => return None,
}
私たちが上記のパターンをtry_opt!(expression)
として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)
:
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
はフラグメント指定子と呼ばれます 。 $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() } { 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;
フォローセット
フラグメント指定子の中には、それに続くトークンが、「フォローセット」と呼ばれる制限付きセットの1つでなければならないことが必要です。これにより、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、rest = 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
フラグに似ています)。