マクロ周りの変更
マクロとは、ソースコード中の特定の文字列を別の文字列に置き換える機能です。プログラムの一部から別のプログラムを生成することから、メタプログラミングの手法の一つとされています。
他言語、例えばC/C++言語などではおなじみの機能で、定数または関数のように使えるマクロを定義するために利用できます。
よく使われる標準ライブラリのマクロは、println!です。このマクロは、引数に指定する書式に従って値を標準出力に書き出すものです。関数で実装しても問題ないように思えますが、マクロで実装するのは引数の数を可変にするためです。Rustでは可変長引数がサポートされないので、マクロを使って引数が可変である関数を擬似的に定義するわけです。
Rustには、マクロの定義方法が大きく分けて2つ用意されています。それが「宣言的マクロ」と「手続き的マクロ」です。
Rust 2024では、このうち宣言的マクロのフラグメント識別子の取り扱いについて変更がありました。
フラグメント識別子exprの改善
Rust 2024では、宣言的マクロの定義において、フラグメント識別子exprがconstキーワードと「_」自身を式の一部として認識するようになりました。
宣言的マクロとは、Rustの初期バージョンから実装されている基本的なマクロ機能です。シンプルかつ直感的にマクロを記述できますが、より柔軟性の高い手続き的マクロの登場により、使われなくなるもの(deprecated)とされています。そのため、今回の修正は宣言的マクロにおける最低限の問題を修正しているだけと位置付けられるでしょう。
宣言的マクロでは、macro_rules!マクロによって引数の処理方法をルールとして定義します。このとき、各ルールはmatch式のようにパターンとマッチ時の式のペアで記述していきます。パターンは主にメタ変数とフラグメント識別子でセミコロン(:)を挟んだもので記述していきます。
パターン = メタ変数:フラグメント識別子
メタ変数は、マッチしたパターンが入るマクロ内でのみ使える変数です。フラグメント識別子は、フラグメント(処理対象となるソースコードの断片を指す)の種類を指定するもので、主に以下の表に挙げる識別子があります。
種類 | マッチするもの |
---|---|
expr | 式(expression) |
block | ブロック(block)。{~}で囲まれているもの |
ident | Rustの識別子(identifier)。予約語でもマッチする |
item | Rustのアイテム(item)。関数、構造体、列挙子、定数、モジュール、トレイト、use宣言など |
literal | リテラル |
ty | 型(type)。未定義でも文法上許されるものであればよい(予約語は不可) |
通常、パターンにおいてはいずれかのフラグメント識別子により、マッチする対象を指定します。よく使われるのは式や識別子です。
従来エディションでは、フラグメント識別子にexprすなわち式を指定した場合、式がconstキーワードを含む場合や、ワイルドカード(_)を含む場合にマッチできずエラーとなっていました。Rust 2024では、constキーワードや「_」を含む場合にも、exprが期待通りにマッチするようになります。
例えば以下のリストは、従来エディションでは一致するルールがないというコンパイルエラーになっていました。これは、マクロ呼び出し時に引数にconstキーワードを付記していますが、これが式の一部として認識されないためです。
macro_rules! example { ($e:expr) => { println!("{}", $e); }; } fn main() { example!(const { 1 + 1 }); }
Rust 2024では、問題なくコンパイルされ、結果として「2」が表示されます。
「_」も同様に、従来エディションでは以下のリストはコンパイルエラーになります。
macro_rules! example2 { ($e:expr) => { $e = 1; println!("マッチしました"); }; } fn main() { example2!(_); }
Rust 2024では、「_」がexprとして認識されることで問題なくコンパイルされ、結果として「マッチしました」が表示されます。
フラグメント識別子が指定されないときにエラー
Rust 2024では、宣言的マクロの定義においてmissing_fragment_specifierリントが既定でdenyとなり、パターンにフラグメント識別子が指定されないときにエラーになるようになりました。
Rust 2024では、missing_fragment_specifierリントが既定でdenyとなり、フラグメント識別子のないパターンがあるときにコンパイルエラーになります。
macro_rules! sample { () => {}; ($name) => { }; // ここでコンパイルエラー } fn main() { sample!(); sample!(1); }
例えば以下のようにフラグメント識別子を指定すれば、エラーを解消できます。
($name:expr) => { };
従来は、フラグメント識別子が指定されていない場合でも、そのパターンが未使用であればエラーとはなりませんでしたが、Rust 2024では未使用であってもエラーとなります。