読者です 読者をやめる 読者になる 読者になる

Rustは構文解析をしてからマクロを展開する

C言語では字句解析の次が前処理で、前処理のあとに構文解析が行われるが、Rustでは構文解析が終わってからマクロが展開される。

より正確に説明すると、Rustのコンパイルはcrate単位で行われ、crateのコンパイル処理の冒頭部は以下の2フェーズに分かれている。

  1. crateの冒頭から字句解析と構文解析を行う。 mod foo; のようなアイテムがある場合、そのインクルード処理はこのフェーズの中で行われる。これにより1つのcrateに対応する単一のAST(抽象構文木)が生成される。
  2. 構文拡張を展開する。構文拡張にはマクロ呼び出しや #[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を先頭から順に走査することで行われる。走査の途中でマクロ呼び出しに遭遇した場合、以下の処理が行われる。

  1. 構文拡張のデータベースからマクロ定義を検索する。
  2. マクロ定義に、マクロ呼び出しの実引数(トークンツリーの列)を入力する。
  3. 得られた出力(トークンツリーの列)を構文解析する。
  4. 得られたASTに再帰的に構文拡張の展開処理を行う。

まとめ

Rustは構文解析をしてからマクロを展開するため、CではできてしまういくつかのマクロがRustではできない場合がある。これは大抵の場合よい方向にはたらくだろう。