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

% macro_rules!

以上を踏まえて、macro_rules! そのものを紹介できます。前述のとおり、macro_rules!それ自体が構文拡張であり、つまり技術的には Rust の構文の一部ではありません。これは次の形式を使います。

macro_rules! $name {
    $rule0 ;
    $rule1 ;
    // …
    $ruleN ;
}

ルールは少なくとも 1 つ必要で、最後のルールの後のセミコロンは省略できます。

各「rule」は次のようになります。

    ($pattern) => {$expansion}

実際には、丸括弧と波括弧はどの種類のグループでもかまいませんが、パターンを丸括弧で囲み、展開を波括弧で囲むのがある程度慣例です。

気になるかもしれませんが、macro_rules! の呼び出しは……何にも展開されません。少なくとも、AST に現れるものには展開されません。そうではなく、マクロを登録するためにコンパイラー内部の構造を操作します。そのため、技術的には空の展開が有効な任意の位置で macro_rules! を使えます。

マッチング

マクロが呼び出されると、macro_rules! インタープリターはルールを字句上の順序で 1 つずつ処理します。各ルールについて、入力トークンツリーの内容をそのルールの pattern と照合しようとします。パターンは、マッチしたと見なされるためには入力の全体にマッチしなければなりません。

入力がパターンにマッチすると、その呼び出しは expansion に置き換えられます。そうでなければ、次のルールが試されます。すべてのルールがマッチに失敗すると、マクロ展開はエラーで失敗します。

最も単純な例は、空のパターンです。

macro_rules! four {
    () => {1 + 3};
}

これは、入力も空である場合に限りマッチします(つまり four!()four![]、または four!{})。

マクロを呼び出すときに使う特定のグループ化トークンはマッチ対象にならないことに注意してください。つまり、上のマクロは four![] として呼び出しても依然としてマッチします。入力トークンツリーの内容だけが考慮されます。

パターンにはリテラルのトークンツリーも含めることができ、それらは正確にマッチしなければなりません。これは、単に通常どおりトークンツリーを書くことで行います。たとえば、シーケンス 4 fn ['spang "whammo"] @_@ にマッチさせるには、次のようにします。

macro_rules! gibberish {
    (4 fn ['spang "whammo"] @_@) => {...};
}

書くことができる任意のトークンツリーを使えます。

キャプチャ

パターンにはキャプチャも含めることができます。これにより、入力をある一般的な文法カテゴリに基づいてマッチさせ、その結果を変数にキャプチャして、出力に代入できるようになります。

キャプチャは、ドル記号($)、識別子、コロン(:)、そして最後にキャプチャの種類を続けて書きます。キャプチャの種類は次のいずれかでなければなりません。

  • item: 関数、構造体、モジュールなどのアイテム
  • block: ブロック(つまり、波括弧で囲まれた、文および/または式のブロック)
  • stmt: 文
  • pat: パターン
  • expr: 式
  • ty: 型
  • ident: 識別子
  • path: パス(例: foo::std::mem::replacetransmute::<_, int>、…)
  • meta: メタアイテム。#[...] 属性および #![...] 属性の内側に入るもの
  • tt: 単一のトークンツリー

たとえば、入力を式としてキャプチャするマクロは次のとおりです。

macro_rules! one_expression {
    ($e:expr) => {...};
}

これらのキャプチャは Rust コンパイラーのパーサーを活用し、常に「正しい」ことを保証します。expr キャプチャは、コンパイル対象の Rust のバージョンにおいて、常に完全で有効な式をキャプチャします。

制限内であれば(後述)、リテラルのトークンツリーとキャプチャを混在させることができます。

キャプチャ $name:kind は、$name と書くことで展開に代入できます。例:

macro_rules! times_five {
    ($e:expr) => {5 * $e};
}

マクロ展開とよく似て、キャプチャは完全な AST ノードとして代入されます。これは、$e にどのようなトークン列がキャプチャされても、それが単一の完全な式として解釈されることを意味します。

1 つのパターンに複数のキャプチャを持たせることもできます。

macro_rules! multiply_add {
    ($a:expr, $b:expr, $c:expr) => {$a * ($b + $c)};
}

繰り返し

パターンには繰り返しを含めることができます。これにより、トークンのシーケンスをマッチさせることができます。繰り返しは一般に $ ( ... ) sep rep という形式です。

  • $ はリテラルのドル記号トークンです。
  • ( ... ) は、繰り返される丸括弧でグループ化されたパターンです。
  • sep任意の区切りトークンです。一般的な例は ,; です。
  • rep必須の繰り返し制御です。現在、これは *(0 回以上の繰り返しを示す)または +(1 回以上の繰り返しを示す)のどちらかです。「0 回または 1 回」や、その他のより具体的な回数や範囲を書くことはできません。

繰り返しには、リテラルのトークンツリー、キャプチャ、他の繰り返しなど、任意の有効なパターンを含めることができます。

繰り返しは展開でも同じ構文を使います。

たとえば、以下は各要素を文字列としてフォーマットするマクロです。0 個以上のカンマ区切りの式にマッチし、ベクターを構築する式に展開されます。

macro_rules! vec_strs {
    (
        // 繰り返しを開始:
        $(
            // 各繰り返しには式が含まれていなければなりません...
            $element:expr
        )
        // ...カンマで区切られ...
        ,
        // ...0 回以上繰り返されます。
        *
    ) => {
        // 複数の文を使えるように、展開をブロックで囲みます。
        {
            let mut v = Vec::new();

            // 繰り返しを開始:
            $(
                // 各繰り返しには次の文が含まれ、
                // $element は対応する式に置き換えられます。
                v.push(format!("{}", $element));
            )*

            v
        }
    };
}

fn main() {
    let s = vec_strs![1, "a", true, 3.14159f32];
    assert_eq!(&*s, &["1", "a", "true", "3.14159"]);
}