フラグメント指定子 (Frangment Specifiers)
体系的説明の章で触れたように、Rust(1.60時点)には14種類のフラグメント指定子があります。 本節ではその一部についてもう少し詳しく説明し、各マッチパターンに一致する入力例を示します。
Note:
ident
,lifetime
,tt
以外のすべてのフラグメントによる捕捉は、抽象構文木を不透明(opaque)にします。これにより、その抽象構文木は後のマクロ呼び出しにおいてそれ以上フラグメント指定子にマッチしなくなります。
block
block
フラグメントは、開き波かっこ {
・任意の数の文・閉じ波かっこ }
の並びからなるブロック式のみにマッチします。
macro_rules! blocks { ($($block:block)*) => (); } blocks! { {} { let zig; } { 2 } } fn main() {}
expr
expr
フラグメントは、任意の種類の式にマッチします (Rustは「式指向 (expression oriented)」の言語なので、たくさんの種類の式があります)。
macro_rules! expressions { ($($expr:expr)*) => (); } expressions! { "literal" funcall() future.await break 'foo bar } fn main() {}
ident
ident
フラグメントは、識別子または予約語にマッチします。
macro_rules! idents { ($($ident:ident)*) => (); } idents! { // _ <- This is not an ident, it is a pattern // _ <- これは識別子ではなくパターン foo async O_________O _____O_____ } fn main() {}
item
item
フラグメントは、任意のRustのアイテムの定義のみにマッチし、アイテムを参照する識別子にはマッチしません。
これには可視性修飾子を含みます。
macro_rules! items { ($($item:item)*) => (); } items! { struct Foo; enum Bar { Baz } impl Foo {} pub use crate::foo; /*...*/ } fn main() {}
lifetime
lifetime
フラグメントは、ライフタイムとラベルにマッチします。
ライフタイムは、'
が先頭につくことを除きident
と非常に似ています。
macro_rules! lifetimes { ($($lifetime:lifetime)*) => (); } lifetimes! { 'static 'shiv '_ } fn main() {}
literal
literal
フラグメントは、任意のリテラル式にマッチします。
macro_rules! literals { ($($literal:literal)*) => (); } literals! { -1 "hello world" 2.3 b'b' true } fn main() {}
meta
meta
フラグメントは、属性の中身にマッチします。
すなわち、それは単純パス(simple path)と呼ばれるジェネリック引数を持たないパスの後ろに、かっこで括られたトークン木または =
とリテラル式の並びが続いたものにマッチします
Note: 通常、このフラグメントは、属性を捕捉するための #[$meta:meta]
または#![$meta:meta]
といったマッチパターンの中で使われます。
macro_rules! metas { ($($meta:meta)*) => (); } metas! { ASimplePath super::man path = "home" foo(bar) } fn main() {}
Docコメントの真実:
/// ...
や!// ...
のようなDocコメントは、実は属性の構文糖衣なのです! これらはそれぞれ#[doc="..."]
や#![doc="..."]
という形に脱糖されます。 つまり、Docコメントに対して属性と同様のマッチングが行えるということです!
pat
pat
フラグメントは、任意の種類のパターンにマッチします。これには、Rust 2021 エディションで追加されたorパターンを含みます。
macro_rules! patterns { ($($pat:pat)*) => (); } patterns! { "literal" _ 0..5 ref mut PatternsAreNice 0 | 1 | 2 | 3 } fn main() {}
pat_param
Rust 2021 エディションにおいて、orパターンのパースを可能にするために pat
フラグメントの動作が変更されました。
pat
フラグメントに続けられるもののリストが変更され、|
トークンを続けることができなくなりました。
この問題を回避する、または従来の動作を取り戻すのに、pat_param
フラグメントが使えます。これには |
を続けることができますが、一方でトップレベルのorパターンには使えません。
macro_rules! patterns { ($( $( $pat:pat_param )|+ )*) => (); } patterns! { "literal" _ 0..5 ref mut PatternsAreNice 0 | 1 | 2 | 3 } fn main() {}
path
path
フラグメントは、TypePathと呼ばれるスタイルのパスにマッチします。
これには関数スタイルのトレイト Fn() -> ()
を含みます。
macro_rules! paths { ($($path:path)*) => (); } paths! { ASimplePath ::A::B::C::D G::<eneri>::C FnMut(u32) -> () } fn main() {}
stmt
stmt
フラグメントは文のみにマッチします。(Unit構造体のような)セミコロンが必須のアイテム文の場合を除き、末尾のセミコロンにはマッチしません。
簡単な例によって、これが正確には何を意図しているのかを示します。 捕捉したものをそのまま出力するだけのマクロを使います:
macro_rules! statements {
($($stmt:stmt)*) => ($($stmt)*);
}
fn main() {
statements! {
struct Foo;
fn foo() {}
let zig = 3
let zig = 3;
3
3;
if true {} else {}
{}
}
}
例えばplaygroundを使って1、これを展開すると、おおよそ次のようなものが得られます:
/* snip */
fn main() {
struct Foo;
fn foo() { }
let zig = 3;
let zig = 3;
;
3;
3;
;
if true { } else { }
{ }
}
この結果からいくつかのことがいえます。
まず、すぐにわかることは、stmt
フラグメントが末尾のセミコロンを捕捉しないにもかかわらず、必要なところにセミコロンが出力されているということです。これは、すでに文の末尾にセミコロンがあったとしても同様です。
この理由は単純で、セミコロンはそれ自体がすでに、stmt
フラグメントが捕捉する妥当な文だからです。
よって、このマクロは8回ではなく10回捕捉を行っているのです!
これは複数回の繰り返しを行い、それらを一つの繰り返し展開形として展開する際に重要となりえます。そのようなケースでは繰り返しの回数が一致している必要があるためです。
もう一つわかることは、アイテム文struct Foo;
の末尾のセミコロンにマッチしているということです。そうでなければ、他の場合のように余計なセミコロンが出てくるはずです。
前述したように、セミコロン必須のアイテム文に関しては末尾のセミコロンがマッチ対象になるため、筋は通っています。
最後に、単独のブロック式や制御フロー式のみから構成される場合を除き、式が末尾のセミコロンつきで出力され直していることが観察できます。
ここで言及したことの詳細を知るには、リファレンスをあたってください。
幸い、これらの詳細は通常まったく重要ではありません。先述したように繰り返しに関する小さな例外はありますが、それだけではそこまで頻繁には問題にならないはずです。
デバッグを行う際のコツについてはデバッグの章を参照。
tt
tt
フラグメントは1つのトークン木にマッチします。
トークン木が正確には何なのかについて復習したければ、本書のトークン木の章を読み返すとよいでしょう。
tt
フラグメントは最も強力なフラグメントの一角です。ほぼすべてのものにマッチさせられるうえに、あとでその中身を調べる余地を残すこともできるためです。
このフラグメントは、tt-muncherやプッシュダウン累算器 (push-down-accumulator) といった非常に強力なパターンを可能にします。
ty
ty
フラグメントは、任意の種類の型の式 (type expression) にマッチします。
macro_rules! types { ($($type:ty)*) => (); } types! { foo::bar bool [u8] impl IntoIterator<Item = u32> } fn main() {}
vis
vis
フラグメントは空かもしれない可視性修飾子 (Visibility qualifier) にマッチします。
#![allow(unused)] fn main() { macro_rules! visibilities { // ∨~~Note this comma, since we cannot repeat a `vis` fragment on its own // v~~このコンマが必要なことに注意。`vis`フラグメント単体を繰り返すことはできないため。 ($($vis:vis,)*) => (); } visibilities! { , // no vis is fine, due to the implicit `?` // 暗黙の `?` により、可視性なしでも問題ない pub, pub(crate), pub(in super), pub(in some_path), } }
空のトークン列にマッチさせられるとはいえ、以下に説明するように、vis
フラグメントは選択的繰り返しとは大きく異なった動作をします。
vis
に空トークン列をマッチさせたあとに余るトークンがない場合、マクロのマッチング全体が失敗します。
#![allow(unused)] fn main() { macro_rules! non_optional_vis { ($vis:vis) => (); } non_optional_vis!(); // ^^^^^^^^^^^^^^^^ error: missing tokens in macro arguments }
$vis:vis $ident:ident
は問題なくマッチしますが、$(pub)? $ident:ident
は pub
が妥当な識別子であるために曖昧なのでマッチしません。
#![allow(unused)] fn main() { macro_rules! vis_ident { ($vis:vis $ident:ident) => (); } vis_ident!(pub foo); // this works fine // これは問題なく動く macro_rules! pub_ident { ($(pub)? $ident:ident) => (); } pub_ident!(pub foo); // ^^^ error: local ambiguity when calling macro `pub_ident`: multiple parsing options: built-in NTs ident ('ident') or 1 other option. }
空のトークン列にマッチするフラグメントは、tt
フラグメントや再帰的展開と組み合わせることでとても興味深い奇妙な動作を生み出します。
空のトークン列にマッチする場合であっても、vis
指定のメタ変数はやはりキャプチャとみなされ、さらに tt
, ident
, lifetime
フラグメントでないため以降の展開に対して不透明になります。
このキャプチャを別のマクロを呼び出す際に tt
として渡すと、実質的に何も含まないトークン木が手に入ることになります。
macro_rules! it_is_opaque { (()) => { "()" }; (($tt:tt)) => { concat!("$tt is ", stringify!($tt)) }; ($vis:vis ,) => { it_is_opaque!( ($vis) ); } } fn main() { // this prints "$tt is ", as the recursive calls hits the second branch with // an empty tt, opposed to matching with the first branch! // "$tt is " と表示される。再帰呼び出しが1番めの分岐にマッチしようとせず、 // 空のトークン木(tt)を伴って2番めの分岐に到達するため println!("{}", it_is_opaque!(,)); }