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
}
}
}