三大サンダー!

ブログです

Rustの宣言的マクロ

Rustのコードを書いていて、マクロが頻出するものの理解が不足しており少しばかり気味悪さを覚えていた。
内容を調べる気力が湧いたため、少しばかりRustのマクロについて調べることにする。
本稿はRustのマクロの種類のうち宣言的マクロについての内容を調べたことのメモ書きである。


さて、Rustには宣言的マクロ手続き的マクロという大きく2つの分類のマクロが存在している。
宣言的マクロprintln!vec!などといった、suffixに!のつくものがそれである。

宣言的マクロを使うメリット

このマクロを利用することのメリットは関数では扱えない可変長引数を扱うことが可能ということ。
以下でprintln!マクロを例に見てみる。

// 引数1つでも呼び出せる
println!("hello");

// 引数2つでも呼び出せる
let greet = "hello";
println!("{} world", greet);

Rustの関数ではこのような可変長引数を扱うことが出来ないため、可変長の引数をとって何かをするような処理を行いたい場合に宣言的マクロを定義するメリットがある。

宣言的マクロの定義方法

宣言的マクロを定義するにはmacro_rules!というキーワードを利用する。
以下に、与えた引数に対してprintln!マクロを実行するマクロを記す。

macro_rules! multi_println {
    ( $( $x:expr ), * ) => {
        $(
            println!($x);
        )*
    }
}

これを呼び出すと以下のように出力される。

multi_println!("a", "b", "c");
// a
// b
// c

これはどのようにして、動作しているのだろうか?
宣言的マクロでは与えられたパラメタをmatch式のようなパターンマッチで評価し、評価されたブロック内のコードに置き換えるという動きをする。
詳しいmatchパターンはまだドキュメントを流し見しただけであり、理解が追いついていないのだが、先に記したコードでは、
( $($x:expr), * )がmatchパターンになり、
$xにはmatchした値が入り、exprはmatchする種別、*正規表現のように先のmatchパターンに0個以上matchすること、という意味となる。

matchした後のブロック内のコードは実際に展開されるコードであり、$( ... )はマッチした回数呼び出される処理となる。
先に記したコードmulti_println!("a", "b", "c");は以下の実装と同等である。

println!("a");
println!("b");
println!("c");

宣言的マクロは先に記したコードのように定義するだけでは、定義したスコープ内でしか利用することが出来ない。
スコープ外でも利用可能にするには#[macro_export]という注釈をマクロ定義の前に付ける必要がある。

// #[macro_export]を付けておけば、そのクレートの利用を宣言することでマクロも利用可能になる。
#[macro_export]
macro_rules! sample { ... }

宣言的マクロはいつ定義すればよいのか?

ドキュメントによれば、この宣言的マクロの定義方法はアップデートによってduplicateになるとされている。

macro_rules!には、いくつかの奇妙なコーナーケースがあります。 将来、Rustには別種の宣言的マクロが登場する予定です。これは、同じように働くけれども、それらのコーナーケースのうちいくらかを修正します。 そのアップデート以降、macro_rules!は事実上非推奨 (deprecated) となる予定です。

マクロ - The Rust Programming Language 日本語版 - https://doc.rust-jp.rs/book-ja/ch19-06-macros.html

ということで、新たに定義するというよりは既存実装を読む上での理解の助けにするぐらいに理解しておけばよく、自前で何か定義するということはあまり考えなくても良いのだろう。


ということで、宣言的マクロについてざっくりと理解を深めることが出来た。 また余裕が出た機会には手続き的マクロについても理解を深めることができればと思う。