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