macro_rules
での入れ子ルールを分かりやすくするための慣習について。
macro_rules
での入れ子ルールは複雑になりうる。
その対策として、ルールのブランチに @
から始まる名前をつけ、サブルールとして利用する例がたまに見られる。
なお、@
から始めるのはただの慣習ではあるが (他と区別できれば何でも良い)、この方法は割と知られているため、他の文字より将来の変更に強いかもしれない。
use std::collections::BTreeMap;
macro_rules! text_map {
{$($key:literal $(: $val: literal)?),*} => {
BTreeMap::<&str, &str>::from_iter([
$(text_map!(@entry $key $(: $val)?)),*
])
};
(@entry $key:literal) => {
($key, "")
};
(@entry $key:literal: $val:literal) => {
($key, $val)
};
}
fn main() {
let map = text_map! {
"foo": "hoge",
"bar": "piyo",
"baz": "fuga",
"qux"
};
assert_eq!(map, BTreeMap::from_iter([
("foo", "hoge"),
("bar", "piyo"),
("baz", "fuga"),
("qux", "")
]));
}
この方法には利点と欠点があるが、筆者としてはやや後者が勝るように思える。利点は見通しの良さ、欠点は公開レベルが本体ルールとサブルールとで同じになる点にある。利点は主にマクロの作者にとってだが、欠点は主にマクロの利用者にとってのものになる。これでは後者を優先したくなるのは仕方なく思える。
複数の macro_rules
を用意しても似た効果が得られる。これならサブルールの公開レベルも調整できる (記述位置が本体ルールと別になるため、サブルール的なニュアンスは少し薄れるかもしれないが…)。
use std::collections::BTreeMap;
macro_rules! text_map {
{$($key:literal $(: $val: literal)?),*} => {
BTreeMap::<&str, &str>::from_iter([
$(private::text_map_entry!($key $(: $val)?)),*
])
}
}
mod private {
macro_rules! text_map_entry {
($key:literal) => {
($key, "")
};
($key:literal: $val:literal) => {
($key, $val)
};
}
pub(crate) use text_map_entry;
}
fn main() {
let map = text_map! {
"foo": "hoge",
"bar": "piyo",
"baz": "fuga",
"qux"
};
assert_eq!(map, BTreeMap::from_iter([
("foo", "hoge"),
("bar", "piyo"),
("baz", "fuga"),
("qux", "")
]));
}