Rustは構文解析をしてからマクロを展開する
C言語では字句解析の次が前処理で、前処理のあとに構文解析が行われるが、Rustでは構文解析が終わってからマクロが展開される。
より正確に説明すると、Rustのコンパイルはcrate単位で行われ、crateのコンパイル処理の冒頭部は以下の2フェーズに分かれている。
- crateの冒頭から字句解析と構文解析を行う。
mod foo;
のようなアイテムがある場合、そのインクルード処理はこのフェーズの中で行われる。これにより1つのcrateに対応する単一のAST(抽象構文木)が生成される。 - 構文拡張を展開する。構文拡張にはマクロ呼び出しや
#[cfg(...)]
や#[derive(...)]
などが含まれている。これによりASTから構文拡張が取り除かれる。
マクロのスキップ
初回の構文解析では、マクロは展開されないままの状態で抽象構文木に格納される。抽象構文木の定義に、マクロをそのまま格納するための構築子が含まれている。
この初回の構文解析では、「マクロ呼び出しの終端はどこか」「マクロ展開後の構文要素の種類は何か」を確定させる必要がある。
終端を探す
Rustの構文には、「マクロ呼び出しとその展開を含む全ての構文要素は、括弧 ()[]{}
の対応が取れている」という重要な制約がある。そのため、正確な構文がわからなくても、括弧の対応を追うことで、マクロの終端を決定することができる。
このため、マクロは「1トークン」を処理することはできず、かわりに「1トークンツリー」単位で処理するようになっている。トークンツリーは、以下のうちの1つである。
構文要素を確定する
構文要素は、マクロ呼び出しの位置により、式、パターン、文、アイテム、トレイトアイテム、実装アイテムのいずれに展開されるかが、展開前に確定する。
ExprKind::Mac(Mac)
… マクロは式に展開される。(文脈によっては、0個か1個の式に展開される)
macro_rules! my_macro { ($a:expr) => {$a + 1} } fn main() { println!("{}", my_macro!(10)); println!("{}", my_macro![10]); println!("{}", my_macro!{10}); }
PatKind::Mac(Mac)
… マクロはパターンに展開される。
macro_rules! my_macro { ($a:pat) => {($a, _)} } fn main() { let my_macro!(x) = (11, 20); println!("{}", x); let my_macro![y] = (11, 20); println!("{}", y); let my_macro!{z} = (11, 20); println!("{}", z); }
TyKind::Mac(Mac)
… マクロは型に展開される。
macro_rules! my_macro { ($a:ty) => {($a, u32)} } fn f(x: my_macro!(u8), y: my_macro![u8], z: my_macro!{u8}) { println!("{:?}, {:?}, {:?}", x, y, z); } fn main() { f((1, 2), (3, 4), (5, 6)); }
StmtKind::Mac(P<(Mac, MacStmtStyle, ThinVec<Attribute>)>)
… マクロは0個以上の文(let束縛またはアイテム定義または式文)に展開される。
macro_rules! my_macro { ($($a:tt)*) => {$($a)*} } fn main() { // (), []の場合は直後にセミコロンが必要だが、 {}の場合は不要。 // このセミコロンは展開後の文の挙動に影響を与える my_macro!(let x = 10;); my_macro!{println!("{}", x + 1);} my_macro![println!("{}", x + 1)]; }
ItemKind::Mac(Mac)
… 0個以上のアイテムに展開される。この種類のマクロ呼び出しは特別に、 !
の直後に識別子を与えることができるが、現在この構文が使えるのは macro_rules!
のみである。
// macro_rules! 自身も特殊なアイテムマクロである macro_rules! my_macro1 { ($($a:tt)*) => {$($a)*} } macro_rules! my_macro2 ( ($($a:tt)*) => {$($a)*} ); macro_rules! my_macro3 [ ($($a:tt)*) => {$($a)*} ]; // (), []の場合は直後にセミコロンが必要だが、 {}の場合は不要。 // このセミコロンはマクロ展開後のアイテムには影響を与えない my_macro3! { fn f1() -> u32 { 32 } } my_macro1!( fn f2() -> u32 { 33 } ); my_macro2![ fn f3() -> u32 { 34 } ]; fn main() { println!("{},{},{}", f1(), f2(), f3()); }
TraitItemKind::Macro(Mac)
… 0個以上のトレイトアイテムに展開される。
macro_rules! my_macro { ($($a:tt)*) => {$($a)*} } trait Foo { // (), []の場合は直後にセミコロンが必要だが、 {}の場合は不要。 // このセミコロンはマクロ展開後のアイテムには影響を与えない my_macro!(fn f1() -> u32;); my_macro!{fn f2() -> u32;} my_macro![fn f3() -> u32;]; } fn main() { }
TraitItemKind::Macro(Mac)
… 0個以上の実装アイテムに展開される。
macro_rules! my_macro { ($($a:tt)*) => {$($a)*} } struct A; impl A { // (), []の場合は直後にセミコロンが必要だが、 {}の場合は不要。 // このセミコロンはマクロ展開後のアイテムには影響を与えない my_macro!(fn f1() -> u32 { 32 }); my_macro!{fn f2() -> u32 { 33 }} my_macro![fn f3() -> u32 { 34 }]; } fn main() { }
構文拡張を展開する
構文拡張は、マクロ呼び出しと属性からなる。これらはコンパイラのフェーズ2で、syntax::ext::expand
で処理される。
展開処理は、ASTを先頭から順に走査することで行われる。走査の途中でマクロ呼び出しに遭遇した場合、以下の処理が行われる。
- 構文拡張のデータベースからマクロ定義を検索する。
- マクロ定義に、マクロ呼び出しの実引数(トークンツリーの列)を入力する。
- 得られた出力(トークンツリーの列)を構文解析する。
- 得られたASTに再帰的に構文拡張の展開処理を行う。
まとめ
Rustは構文解析をしてからマクロを展開するため、CではできてしまういくつかのマクロがRustではできない場合がある。これは大抵の場合よい方向にはたらくだろう。