Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

% AST 内のマクロ

前述のとおり、Rust におけるマクロ処理は AST の構築のに行われます。したがって、マクロを呼び出すために使われる構文は、言語構文の正式な一部でなければなりません。実際、Rust の構文の一部である「構文拡張」形式はいくつか存在します。具体的には、次の形式です(例として):

  • # [ $arg ]; #[derive(Clone)], #[no_mangle], …
  • # ! [ $arg ]; #![allow(dead_code)], #![crate_name="blang"], …
  • $name ! $arg; println!("Hi!"), concat!("a", "b"), …
  • $name ! $arg0 $arg1; macro_rules! dummy { () => {}; }.

最初の 2 つは「属性」であり、言語固有の構成要素(ユーザー定義型に C 互換 ABI を要求するために使われる #[repr(C)] など)と構文拡張(#[derive(Clone)] など)の両方で共有されています。現在、これらの形式を使うマクロを定義する方法はありません。

3 つ目が、私たちの関心対象です。これはマクロで使用できる形式です。この形式はマクロに限定されているわけではない点に注意してください。これは汎用的な構文拡張形式です。たとえば、format! はマクロですが、format!実装するために使われる format_args! はマクロではありません

4 つ目は本質的にはバリエーションであり、マクロでは使用できません。実際、この形式が少しでも使われる唯一のケースは macro_rules! であり、これについても後で再び取り上げます。

3 つ目の形式($name ! $arg)以外をすべて無視すると、疑問は次のようになります。Rust パーサーは、あらゆる可能な構文拡張について $arg がどのような形をしているのかを、どのように知るのでしょうか。答えは、知る必要がないということです。代わりに、構文拡張呼び出しの引数は単一のトークンツリーです。より具体的には、単一の非リーフトークンツリー、つまり (...)[...]、または {...} です。このことを知っていれば、パーサーが次のすべての呼び出し形式をどのように理解できるのかが明らかになるはずです。

bitflags! {
    flags Color: u8 {
        const RED    = 0b0001,
        const GREEN  = 0b0010,
        const BLUE   = 0b0100,
        const BRIGHT = 0b1000,
    }
}

lazy_static! {
    static ref FIB_100: u32 = {
        fn fib(a: u32) -> u32 {
            match a {
                0 => 0,
                1 => 1,
                a => fib(a-1) + fib(a-2)
            }
        }

        fib(100)
    };
}

fn main() {
    let colors = vec![RED, GREEN, BLUE];
    println!("Hello, World!");
}

上記の呼び出しはさまざまな種類の Rust コードを含んでいるように見えるかもしれませんが、パーサーは単に意味のないトークンツリーの集まりとして見ています。これをより明確にするために、これらすべての構文上の「ブラックボックス」を ⬚ に置き換えると、次のようになります。

bitflags! ⬚

lazy_static! ⬚

fn main() {
    let colors = vec! ⬚;
    println! ⬚;
}

繰り返しますが、パーサーは ⬚ について何も仮定しません。その中に含まれるトークンは記憶しますが、それらを理解しようとはしません。

重要なポイントは次のとおりです。

  • Rust には複数種類の構文拡張があります。ここでは macro_rules! 構文によって定義されるマクロについてのみ扱います。
  • $name! $arg という形式のものを見たからといって、それが実際にマクロであるとは限りません。別の種類の構文拡張かもしれません。
  • すべてのマクロへの入力は、単一の非リーフトークンツリーです。
  • マクロ(正確には、構文拡張一般)は、抽象構文木の一部としてパースされます。

余談: 最初の点により、以下で述べることの一部(次の段落を含む)は構文拡張一般に当てはまります。1

最後の点が最も重要です。なぜなら、これには重大な意味合いがあるからです。マクロは AST にパースされるため、明示的にサポートされている位置にのみ出現できます。具体的には、マクロは次のものの代わりに出現できます。

  • パターン
  • アイテム
  • impl アイテム

この一覧に含まれていないものの例:

  • 識別子
  • Match アーム
  • 構造体フィールド
  • 2

最初の一覧にない位置でマクロを使う方法は、まったく、絶対にありません


  1. これは、「syntax extension」よりも「macro」のほうがはるかに素早く簡単に入力できるため、かなり都合がよいです。

  2. 型マクロは、不安定版 Rust で #![feature(type_macros)] を指定すると利用できます。Issue #27336 を参照してください。