この記事で伝えたいこと
Rustマクロは構文「パーツ」だけ出力することはできず、基本的に構文要素全体を出力する必要がある
こちらの記事は Rustマクロ冬期講習アドベントカレンダー 3日目の記事です!
2日目の記事で、Rustマクロについて「出力されるトークン木は設置先のASTに組み込まれて有効なものでなければならない」と述べました。
これに伴って、というわけでもないですが、Rustのマクロとして出力できるものと、できないものがあり、今回のRustマクロシリーズ記事が参考としているThe Little Book for Rust Macrosに記載があるためここで紹介したいと思います。
ここから引用の上意訳
(記事の終盤に差し掛かり)最後に重大な意味を持つため最も大切であるポイントを伝えます。RustマクロはASTへと解析されるため、次に示すサポートされた構文要素の箇所にしか記述することができません。Rustマクロを記述できるのは:
- パターン (
pat
)- 文 (
stmt
)- 式 (
expr
)- アイテム (
item
。これはimpl
ブロックを含む )- 型 (
type
)以上の箇所 のみ になります。
一方で次に示すような箇所ではどう逆立ちしたところでマクロを使用することは不可能です。
- 識別子
- マッチアーム (パターンではなく
=>
を含めたアーム全体を指す)- 構造体のフィールド
イマイチ何が出力できないかわかりにくいと思うので一言で表すと、「ある構文要素の 一部 だけを出力するマクロ」は作れないことになっています。
コードで表すとマクロで表現可能なのは次の場合です。
macro_rules! can {
(pat) => {
Some(_) | None
};
(stmt) => {
let _ = "";
};
(expr) => {
10 + 20 + 30
};
(item; $name:ident, $s:stmt) => {
fn $name() { $s }
};
(type_) => {
Option<u32>
};
}
fn main() {
let can!(pat) = Some(10);
can!(stmt);
let _ = can!(expr) + 40;
can!(item; hoge, println!("hoge"));
hoge();
let _: can!(type_) = Some(10);
}
以下のようなマクロの使い方はできません!
macro_rules! cant {
(ident; $name:ident) => {
$name
};
(match_arm) => {
Some(_) | None => (),
};
(strct_field) => {
hoge: u32,
};
}
fn main() {
struct cant!(ident; Hoge);
match Some(10) {
cant!(match_arm)
}
struct HogeHoge {
cant!(strct_field)
}
}
1つ目も3つ目も生成しているのは構造体全体ではなく構造体の一部で、2つ目も match
式全体ではなくアームだけをマクロで生成しようとしています。
ある構文要素をなすためのパーツであるからといってただちにダメというわけではないです。先ほどの can
マクロでも、ある意味で match
式の一部を記述しているものがあります。
許されないのは、そのパーツを抜き出した時に「構文要素として見做せなさそうなもの」です。
-
struct Hoge {}
のHoge
の部分 -
a => b
という何か -
hoge: u32
という何か
これらは構造体の一部や match
式の一部ではありますが、この断片だけではRustコードとして意味を為しません!Rustのマクロは、このような中途半端な構造は出力できないようになっています。
とはいえ、例えば「列挙体のフィールドの数分 match
式のマッチアームを生成する」マクロというのはしばしば書きたくなります。こういう時は、マクロの出力結果が match
式全体になるようにする、など、部分ではなく全体を出力することで実現しましょう。
まとめ
要は「ヘンテコな出力は許さない」わけです。2日目の記事にも書いたとおり、こういった厳格な性質のおかげで、Rustのマクロは他言語よりも安心して気軽に呼び出すことができます!