こちらの記事は Rustマクロ冬期講習アドベントカレンダー 7日目の記事です!
前回の記事で、「フラグメント指定子に慣れれば宣言マクロは制したようなもの」と言いました。
前回は以下に示すような表しか出さなかったので、今回はもう少し詳細を話していければと思います!
フラグメント指定子 | 例 | 説明 |
---|---|---|
ident |
hoge , self
|
変数名や関数名などの識別子全般 |
expr |
10 + 20 , funcall() , if a { b } else { c }
|
式。いわゆる評価後に値を返す何か |
literal |
10 , "hoge" , true
|
リテラル。言い換えると、ソースコード内で直接表現される固有値 |
stmt |
let a = 10; , struct Hoge; , fn foo() {}
|
ステートメント。文。何かしら意味を持つものの評価値とはならないもの |
ty |
i32 , &'a str , fn() -> ()
|
型 |
path |
std::option::Option , Turbo::<Fish> , ::std::println
|
パス。一見型と同じに見えるが、 :: で接続されたパスを表す意味が強い。例えば ::std::thread::spawn は関数であり型ではなく、これ自体はパスである |
block |
{} |
スコープを作り出す中括弧を含めた全体 |
item |
pub fn foo() {} , use std::fs; , impl Hoge {}
|
use文や関数宣言など。一部は文でもある。アイテム...という名前だとわかりにくいので、「ファイル内で最も外側に(あるいはトークン木の最上位に)配置できる要素」とおぼえておくと良さそう |
pat / pat_param |
Some(ref hoge) , None , 0..10
|
パターンマッチとして使用できる構造。簡単に言えば match 式のアーム部分になり得るもの。 pat_param は古いエディションとの互換性を保つためなので忘れて良い |
lifetime |
'a , 'static , '_
|
ライフタイム |
meta |
derive(Debug) , path = "hoge" , serde
|
属性風マクロ等のアトリビュート(属性)、すなわち #[] の内側に書くことができる要素。めったに出てこないので忘れてよさそう |
vis |
pub , pub(crate)
|
Visibility、すなわち可視性の略。これのみをマッチさせる必要もないためめったに出てこない |
tt |
トークン木 (Token Tree) のことで、 任意の Rustコード片の塊にマッチするワイルドカード。つまりジョーカー |
「フラグメント指定子がどうしても覚えられない!!!!」という筆者のような方向けに、フラグメント指定子判別器を作ったので良かったら使ってみてください!
Rustマクロ フラグメント指定子(ident, expr, item, stmt...)なんもわからん!となったので判別器作った #Rust - Qiita
フラグメント指定子についてどうまとめようか迷っていたのですが、下手にひねってもアレなので今回は素直に記事作成の参考にしている The Little Book of Rust Macros の内容及び掲載例を拝借しつつ、紹介したい順で紹介することにします!
tt
: トークン木
これだけは特殊なフラグメント指定子なので最初に持ってきました。トークン木は(ほぼ) 任意の構文要素 にマッチします。いわばワイルドカードで、トランプで言えばジョーカー、ウノで言えばドロフォー、ポ◯モンで言えばメタ◯ンみたいなやつです。
「なんでトークン木って名前なの?」とか「トークン木ってなんぞ?」って方はRustマクロ冬期講習アドベントカレンダー2日目の記事 Rustマクロの事前知識①「入出力はトークン木」 #Rust - Qiita を参考にしていただけると幸いです。
活用事例としては再帰呼び出しを利用しているマクロにおいて残り部分を渡す、などです。Little Bookの例が一番わかりやすいですね。
macro_rules! mixed_rules {
() => {};
(trace $name:ident; $($tail:tt)*) => {
{
println!(concat!(stringify!($name), " = {:?}"), $name);
mixed_rules!($($tail)*);
}
};
(trace $name:ident = $init:expr; $($tail:tt)*) => {
{
let $name = $init;
println!(concat!(stringify!($name), " = {:?}"), $name);
mixed_rules!($($tail)*);
}
};
}
fn main() {
let a = 42;
let b = "Ho-dee-oh-di-oh-di-oh!";
let c = (false, 2, 'c');
mixed_rules!(
trace a;
trace b;
trace c;
trace b = "They took her where they put the crazies.";
trace b;
);
}
(引用元: https://veykril.github.io/tlborm/decl-macros/patterns/tt-muncher.html)
利用頻度は低いのですがとりあえず「他とは異なる」ことを強調したいため最初に持ってきました。何にでもマッチしますがおそらくフラグメント指定子として最初に出てくる候補ではありません。
識別子 ident
identifier つまり識別子 (変数名や関数名など) またはキーワードにヒットします。
macro_rules! idents {
($($ident:ident)*) => ();
}
idents! {
// _ <- This is not an ident, it is a pattern
foo
async
O_________O
_____O_____
}
名前っぽいやつを渡したい時はだいたいこいつです!
式 expr
expression、すなわち「式」にヒットします。Rustは式指向言語なので、if "式" ( if a { b } else { c }
) などヒットするものが意外と多いです。
macro_rules! expressions {
($($expr:expr)*) => ();
}
expressions! {
"literal"
funcall()
future.await
break 'foo bar
}
リテラル literal
文字列 "aaa"
、数値 10
、 真偽値 true
など、いわゆる言語組み込みの値にマッチします。
macro_rules! literals {
($($literal:literal)*) => ();
}
literals! {
-1
"hello world"
2.3
b'b'
true
}
文 stmt
文、すなわちプログラムとして意味はあるものの式とは違って値を返さないとされるもの( ()
を返してるとも受け取れる...?)にヒットします。
macro_rules! statements {
($($stmt:stmt)*) => ($($stmt)*);
}
fn main() {
statements! {
struct Foo;
fn foo() {}
let zig = 3
let zig = 3;
3
3;
if true {} else {}
{}
}
}
今回のLittle Bookが掲載している例は興味深いのでちょっと掘り下げようと思います。
マクロが展開された結果(もとい、文としてキャプチャして文として出力し直した結果)はこうなるそうです。
/* snip */
fn main() {
struct Foo;
fn foo() { }
let zig = 3;
let zig = 3;
;
3;
3;
;
if true { } else { }
{ }
}
セミコロンの数が何やら不自然ですね。一つ一つ見てみましょう!
-
struct Foo;
: ユニット型系構造体はセミコロンなし(struct Foo
)では文にならないので、struct Foo;
まででマッチしてこれがそのまま出力される -
fn foo() {}
: これで文になっているのでそのまま出力される -
let zig = 3
: これで文になっているのでキャプチャされ、出力時に セミコロンが付与されている -
let zig = 3;
: セミコロンより前の時点で文としてマッチして、 セミコロンが付与される ので結果としてここでセミコロンが増えている -
3
: リテラルによる式は文として扱われてマッチするので、 セミコロンが付与される 。2回目で上記のlet zig = 3
と同様に余計なセミコロンが増えている -
if true {} else {}
,{}
: 同じ式でも、ブロックにより区切られるためセミコロンを必要としないものたちにはセミコロンは付与されない
型 ty
型にマッチします。 std::option::Option
みたいな普通のものだけでなく、 &'static dyn str
や fn() -> ()
(関数ポインタ)みたいな型もヒットします。
macro_rules! types {
($($type:ty)*) => ();
}
types! {
foo::bar
bool
[u8]
impl IntoIterator<Item = u32>
}
パス path
型と似ているけども、use
文などで使用する ::
でつなげられた識別子の集まりがヒットします。なぜか Fn() -> ()
系のやつもヒットするらしい。謎
macro_rules! paths {
($($path:path)*) => ();
}
paths! {
ASimplePath
::A::B::C::D
G::<eneri>::C
FnMut(u32) -> ()
}
ブロック block
中括弧で区切られスコープを作り、評価されると最後の式の値になる { }
<- こいつのことです!
macro_rules! blocks {
($($block:block)*) => ();
}
blocks! {
{}
{
let zig;
}
{ 2 }
}
アイテム item
構造体や列挙体、関数、定数の定義を行う部分にヒットします。
macro_rules! items {
($($item:item)*) => ();
}
items! {
struct Foo;
enum Bar {
Baz
}
impl Foo {}
pub use crate::foo;
/*...*/
}
定義類は文でもありがちなので文との違いがわかりにくいですが、アイテムは「 fn main() {}
の外側に書くことができる構文要素」と思っておくと覚えやすいです!
パターン pat
/ pat_param
パターンは match
アームの =>
の左側にある部分のことを指します。実は match
式だけではなく let
文や if let
文、関数の引数部分など、 match
アーム以外にもパターンを活用できるシーンは多々あったりします。
macro_rules! patterns {
($($pat:pat)*) => ();
}
patterns! {
"literal"
_
0..5
ref mut PatternsAreNice
0 | 1 | 2 | 3
}
旧エディションとの互換性のために pat_param
というものも設けられていますが気にしなくて大丈夫です。
ライフタイム lifetime
ライフタイムにヒットします。
macro_rules! lifetimes {
($($lifetime:lifetime)*) => ();
}
lifetimes! {
'static
'shiv
'_
}
メタ meta
一番馴染みが薄い構文要素かもしれません。属性風マクロ #[...]
の ...
の部分がメタです。というわけで良く書くメタは例えば derive(Debug, Clone, Copy)
とか。
macro_rules! metas {
($($meta:meta)*) => ();
}
metas! {
ASimplePath
super::man
path = "home"
foo(bar)
}
docコメントの正体
Little Bookの方で差し込まれていた豆知識だったのでこちらでも書くと、
struct Hoge {
/// ドキュメントコメント
field: u32,
}
みたいにつける ///
で始まるドキュメントコメントは、実は属性 #[doc = "..."]
の糖衣構文だったりします!
だから通常のコメントと違ってコンパイラに解析されエラーになることがあるのです。
可視性 vis
pub
や pub(crate)
といった、アイテムが他のモジュールから見えるかを指定する可視性にヒットします。
macro_rules! visibilities {
// ∨~~Note this comma, since we cannot repeat a `vis` fragment on its own
// (visの後にカンマを直接つなげることはできないので、メタ変数側に取り込んで無理やりトークンを消費する)
($($vis:vis,)*) => ();
}
visibilities! {
, // no vis is fine, due to the implicit `?`
pub,
pub(crate),
pub(in super),
pub(in some_path),
}
本稿では説明を省略しますが、「無」にもマッチする珍しい指定子なのでマクロでもおもしろ挙動をするようです。ちなみに何も指定していない時の可視性は pub(self)
と等しく、プライベートなアイテムになっています。
ref: https://veykril.github.io/tlborm/decl-macros/minutiae/fragment-specifiers.html#vis
まとめ・所感
うん!やっぱり多い!フラグメント指定子を使用頻度順に解説しましたがここまで真面目に全部の解説を読んだ人なんていないでしょう。え、読んだ?ありがとうございます。
何はともあれ、慣れてきたら ident
とか expr
ばかりになるでしょうし、わからなくなったら筆者が作ったツールを使えば対応する指定子がすぐわかるので(しつこいステマ)、忘れた時に調べ直すことはあってもまともに覚えている必要はないでしょう。
このフラグメント指定子についてさえ慣れれば、宣言マクロもそしてこれからやる手続きマクロもスラスラ書けるようになります!ぜひ慣れていきましょう!