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", "")
    ]));
}

利点と欠点

この方法には利点と欠点があるが、筆者はやや後者が勝るように思う。利点は見通しの良さ、欠点は rustdoc にてサブルールが本体ルールと同じ階層で掲載される点にある。利点は主にマクロの作者にとってだが、欠点は主にマクロの利用者にとってのものになる。これでは後者を優先したくなるのは仕方なく思える。

代替案

複数の macro_rules を用意しても似た効果が得られる (記述位置が本体ルールと別になるため、サブルール的なニュアンスは少し薄れるかもしれないが…)。

サンプル


use std::collections::BTreeMap;

macro_rules! text_map {
    {$($key:literal $(: $val: literal)?),*} => {
        BTreeMap::<&str, &str>::from_iter([
            $(text_map_entry!($key $(: $val)?)),*
        ])
    }
}

macro_rules! text_map_entry {
	($key:literal) => {
		($key, "")
	};
	($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", "")
    ]));
}