Rustのマクロで識別子(ident)を文字列に変換する

以前マクロの練習のためにHashMapやBTreeMapを生成するマクロを作った。

github.com

マクロ箇所だけ抽出するとこんな感じ

macro_rules! hash_map {
    ($($k:expr => $v:expr),*) => {{
        let mut _m = std::collections::HashMap::new();
        $(_m.insert($k, $v);)*
        _m
    }};
}

JavaScriptのオブジェクトリテラルのように キーはダブルクォートで囲わない記法ができると格好良い。

let m = hash_map!(a => 1,  b => 2,  c => 3);
println!("{:#?}", m);
{
    "b": 2,
    "c": 3,
    "a": 1,
}

これを行うにはmacro_rulesで識別子を文字列に変換する必要あり。 結論としては stringifyマクロを使えばOK。C#のnameof式に近い動きをしてくれそう。

macro_rules! hash_map {
    ($($ik:ident=> $v:expr),*) => {{
        let mut _m = std::collections::HashMap::new();
        $(_m.insert(stringify!($ik), $v);)*
        _m
    }};
}

キーが文字列でも識別子でも動くようにしたバージョン

macro_rules! hash_map {
    ($($ik:ident => $v:expr),*) => {
        hash_map!($(stringify!($ik) => $v),*)
    };

    ($($k:expr => $v:expr),*) => {{
        let mut _m = std::collections::HashMap::new();
        $(_m.insert($k, $v);)*
        _m
    }};
}

exprのほうを上に持ってくると動作しなくて少しハマった。多分識別子で指定したマクロはexprのほうにもマッチするからだと思う。

参考までにHashMapやBTreeMapを作るクレートは既存のものが存在する。

github.com

特別な事情がない限りこちらを使うべきでしょう。こちらのマクロはキャパシティを計算してくれるし。