TL;DR

  • マクロを使う一番の理由は、同じ記述を避けることである。
  • 同じ値の処理は関数に、複数の型の処理はジェネリクスに、同じ構造を持つコードはマクロにまとめる。

内容

項目28 分別をもってマクロを使おうのうち、マクロを使うべき場面の話である。

同じ記述を避ける

マクロを使う一番の理由は、同じ記述を避けることである。これは、普段のプログラミングで行っているカプセル化の延長にすぎない。特定の型の複数の値に同じコードを書いているなら、関数に切り出す。複数の型に同じコードを書いているなら、トレイト境界を持つジェネリクスにする。同じ構造を持つコードを繰り返し書いているなら、マクロにする。

使用例

1 つ目は、enum から特定のバリアントの値をすべて取り出す処理である。

#[macro_export]
macro_rules! values_of_type {
    { $values:expr, $variant:ident } => {
        {
            let mut result = Vec::new();
            for val in $values {
                if let Multi::$variant(v) = val {
                    result.push(v.clone());
                }
            }
            result
        }
    }
}
 
let ints = values_of_type!(&values, Int);   // [1000]
let bytes = values_of_type!(&values, Byte); // [1, 2]

2 つ目は、HTTP のステータスコードを表すデータ構造である。コード、グループ、説明文の組を並べるだけで、Status の enum、group と text のメソッド、TryFrom<i32> の実装をまとめて生成する。

#[macro_export]
macro_rules! http_codes {
    { $( $name:ident => ($val:literal, $group:ident, $text:literal), )+ } => {
        #[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
        #[repr(i32)]
        enum Status {
            $( $name = $val, )+
        }
        impl Status {
            fn group(&self) -> Group {
                match self {
                    $( Self::$name => Group::$group, )+
                }
            }
            fn text(&self) -> &'static str {
                match self {
                    $( Self::$name => $text, )+
                }
            }
        }
        impl core::convert::TryFrom<i32> for Status {
            type Error = ();
            fn try_from(v: i32) -> Result<Self, Self::Error> {
                match v {
                    $( $val => Ok(Self::$name), )+
                    _ => Err(())
                }
            }
        }
    }
}

3 つ目は、失敗したときにエラーの位置(ファイル名と行)と内容を出力する処理である。

macro_rules! log_failure {
    { $e:expr } => {
        {
            let result = $e;
            if let Err(err) = &result {
                eprintln!("{}:{}: operation '{}' failed: {:?}",
                    file!(), line!(), stringify!($e), err);
            }
            result
        }
    }
}

キーワード