2018/11/22

RustでPythonの関数のようなマクロを作ってみました

set!とdict!

Rustでは、びっくりマークが後ろに付いているものはマクロです。学習を始めて最初に使うマクロはprintln!だと思います。println!は、改行付きの出力となっており、もう一つprint!という改行なしの出力というものもあります。その他にもいくつかマクロが標準で使えるようになっていますが、自分自身でもマクロを作成することが可能です。

例えばPythonをよく使う方であるならば、辞書はよく使うものではないでしょうか?
Rustでは同じようなものとして、BTreeMapHashMapがあります。それぞれ特徴が違います。しかし今回はマクロのことがメインなので、マクロのことを書きます。
Pythonで辞書は以下のようになっています。

>>> d = {1: 'A', 2: 'B', 3: 'CDF'}
>>> d
{1: 'A', 2: 'B', 3: 'CDF'}
>>> d2 = dict([(1, 'A'), (2, 'B'), (3, 'CDF')])
>>> d2
{1: 'A', 2: 'B', 3: 'CDF'}

しかしRustでは波括弧やコロンは構文上、同様の使い方はできません。Pythonistaの方々ならひょっとすると慣れ親しんだ波括弧を使いたいかもしれません。そこでマクロを使います。同様に波括弧を使う集合のほうも作ってみました。

use std::collections::{BTreeSet, HashSet, BTreeMap, HashMap};

macro_rules! set {
    ({ $( $x:tt ),* }) => {
        vec![ $( $x ),* ].into_iter().collect()
    };
}

macro_rules! dict {
    ({ $( $k:tt : $v:tt ),* }) => {
        vec![ $( ($k, $v) ),* ].into_iter().collect()
    };
}

fn main() {
    let bts: BTreeSet<i32> = set!({1, 2, 3, 1, 2, 4});
    let tpl_bts: BTreeSet<(u32, u32)> = set!({(1, 2), (1, 2)});
    println!("{:?}", bts);
    println!("{:?}", tpl_bts);
    let hs: HashSet<&str> = set!({"Tom", "Mike", "Mike", "Tom"});
    println!("{:?}", hs);
    let btm: BTreeMap<u32, &str> = dict!({1: "Tom", 2: "Mike"});
    println!("{:?}", btm);
    let hm: HashMap<&str, &str> = dict!({"name": "Tom", "email": "tom@tom.com"});
    println!("{:?}", hm);
}

Rust playgroundのほうで、こちらのコードをコピペして実行していただくと結果を出力します。確認してみてください。上記のマクロは簡単にいうと、Pythonの書き方でRustのコレクションを作成するマクロといった感じでしょうか。ちなみにmacro_rules!もまたマクロで、標準で搭載されているマクロの一つです。そして、:ttという部分は、フラグメント型と呼ばれていて、macro_rules!でサポートされている、パターンマッチに必要なものです。今回使っている:ttは、tokentreeというフラグメント型です。Rustで波括弧はブロックを表すものなので、これ以外のフラグメント型では上記のような形では扱えません。
波括弧を扱うことができるようになると、JSONファイルもまた同様に扱うことが可能になるような気が致します。私はまだまだマクロに関して不勉強ではありますが、こういうことが可能であるということを知っておくだけでもいいのではないでしょうか。