宣言的マクロと手続き的マクロ――Rustのマクロ機能を使ってみる基本からしっかり学ぶRust入門(18)

メタプログラミングの手法に、C/C++言語で普通に使われているマクロ機能があります。最終回である今回は、それらより安全な実装となっているRustのマクロ機能について。

» 2022年12月23日 05時00分 公開

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「基礎からしっかり学ぶRust入門」のインデックス

連載:基礎からしっかり学ぶRust入門

 本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。具体的な利用方法は連載第1回を参考にしてください。


マクロとは?

 マクロとは、ソースコード中の特定の文字列を別の文字列に置き換える機能です。プログラムの一部から別のプログラムを生成することから、メタプログラミングの手法の一つと言われています。C/C++言語などではおなじみの機能で、定数または関数のように使えるマクロを定義するために利用できます。

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のマクロは大きく分けて2種類

 本連載では、ほぼ毎回、Rust標準のマクロを使用してきました。それがprintln!マクロです。このマクロは、引数に指定する書式に従って値を標準出力に書き出す、というものでした。関数で実装してもよい(C言語のprintf関数などは関数で実装されている)ように思えますが、なぜマクロなのでしょうか?

 マクロにする理由の一つは、引数の数を可変にするためです。C言語などと異なり、Rustでは引数の数が可変である関数を定義できません。そのため、マクロを使って、このような関数を疑似的に定義するのです。また、構造体や関数の定義のブロックを受け取り、それに基づき別のコードを展開する機能もRustのマクロにはあります。

 Rustではマクロ定義の方法が大きく分けて2つ用意されています。それが宣言的マクロと手続き的マクロです。以降、2つのマクロについて紹介していきます。

宣言的マクロ

 まずは、宣言的マクロ(Declarative Macro)です。宣言的マクロは、Rustの初期段階の実装から使うことができた基本的なマクロで、macro_rules!構文で定義できます(これ自体もマクロです)。宣言的マクロでは、match式に似た形式で処理内容を定義します。宣言的マクロは、利用者が参照できる場所であれば、どこでも定義可能です。

実装するマクロの内容

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。