メタプログラミングの手法に、C/C++言語で普通に使われているマクロ機能があります。最終回である今回は、それらより安全な実装となっているRustのマクロ機能について。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
マクロとは、ソースコード中の特定の文字列を別の文字列に置き換える機能です。プログラムの一部から別のプログラムを生成することから、メタプログラミングの手法の一つと言われています。C/C++言語などではおなじみの機能で、定数または関数のように使えるマクロを定義するために利用できます。
C/C++言語のマクロは、特に関数の代わりに使われるときに期待しない動作となり、しばしばバグの原因となったり、マクロの危険性の理由となったりしてきました。例えば「#define ADD2(a, b) a + b」のように定義されたマクロADD2を、「int x = ADD2(2, 3) * 4;」のように使用すると、計算結果は「14」と期待外になってしまいます(期待される結果は20です)。
これは、マクロが単なる文字列として置き換えられるためで、C言語のプリプロセッサ(ソースコードをテキストレベルで前処理するプログラム)は変数xの宣言文を「int x = 2 + 3 * 4;」のように置き換えます。マクロの呼び出し結果がそのまま代入されていれば問題ないのですが、加算より優先順位の高い乗算が式に含まれていたため、そちらの演算が優先されて結果が期待外になったわけです。
これを解決するには、マクロの定義を「#define ADD2(a, b) (a + b)」のように変更します。マクロ全体がカッコで囲まれるので、常に優先して演算が実行されるようになり、期待される結果を得られます。しかし、カッコで囲むかどうかはプログラマーに任せられることになるので、ルールとして強制しても漏れが発生する可能性があります。
Rustでは、このような問題が起きないように、言語仕様で安全なマクロの記述を可能にしています。例えば、マクロで展開された部分で変数を宣言しても、名前が衝突したり、外部から見えたりといったことは発生しません。これは衛生的マクロ(Hygienic Macro)と呼ばれ、プログラマーの配慮に依存しない安全なマクロの利用が可能です。
本連載では、ほぼ毎回、Rust標準のマクロを使用してきました。それがprintln!マクロです。このマクロは、引数に指定する書式に従って値を標準出力に書き出す、というものでした。関数で実装してもよい(C言語のprintf関数などは関数で実装されている)ように思えますが、なぜマクロなのでしょうか?
マクロにする理由の一つは、引数の数を可変にするためです。C言語などと異なり、Rustでは引数の数が可変である関数を定義できません。そのため、マクロを使って、このような関数を疑似的に定義するのです。また、構造体や関数の定義のブロックを受け取り、それに基づき別のコードを展開する機能もRustのマクロにはあります。
Rustではマクロ定義の方法が大きく分けて2つ用意されています。それが宣言的マクロと手続き的マクロです。以降、2つのマクロについて紹介していきます。
まずは、宣言的マクロ(Declarative Macro)です。宣言的マクロは、Rustの初期段階の実装から使うことができた基本的なマクロで、macro_rules!構文で定義できます(これ自体もマクロです)。宣言的マクロでは、match式に似た形式で処理内容を定義します。宣言的マクロは、利用者が参照できる場所であれば、どこでも定義可能です。
Copyright © ITmedia, Inc. All Rights Reserved.