Progrust Library.

// dictionary

マクロ.

マクロ(Macro)は、呼び出し箇所がコンパイル時に別のコードへ置き換えられる仕組みです。println!やvec!のように名前の末尾へ!を付けて呼び出すため、関数の呼び出しと見分けられます。展開された結果は普通のRustコードとしてコンパイルされるので、型チェックも通常どおり効きます。

利用側でまず目に付く違いは、引数の個数が固定でないことです。コンソール出力で使うprintln!が、渡す値の個数に関わらず同じ名前で書けるのはマクロだからです。

fn main() {
    let items = vec!["りんご", "みかん"]; // 要素はいくつ並べてもよい
    let prices = vec![120; 3]; // 「120を3個」という別の書き方も受け付ける

    println!("{}種類", items.len()); // 引数1個
    println!("{}と{}", items[0], items[1]); // 引数2個
    println!("価格: {:?}", prices);
}
Playgroundで開く

関数との違い

観点 関数 マクロ
書き方 foo(...) foo!(...)
引数の個数 定義で固定 呼び出しごとに変えられる
処理されるタイミング 実行時に呼ばれる コンパイル時に展開される
受け取るもの 値(式の評価結果) トークン列(型やアイテムの定義なども書ける)
得意なこと 値の計算 コードの生成(トレイト実装の自動生成など)

値ではなくトークン列を受け取るため、ベクタ(Vec)を作るvec![120; 3]の;のように、関数の引数リストには書けない構文も使えます。

宣言的マクロと手続き的マクロ

マクロは定義の仕方で2種類に分かれます。利用側にとっての違いは、呼び出しの見た目と、依存の追加が要るかどうかです。

種類 利用側の書き方 例
宣言的マクロ foo!(...) vec! / println!
手続き的マクロ foo!(...) / #[derive(Foo)] / #[foo] sqlx::query! / #[derive(Serialize)] / #[tokio::main]

宣言的マクロはmacro_rules!で定義するもので、標準ライブラリが提供するマクロの多くはこの形式です(println!などの整形マクロは、内部でコンパイラ組み込みのformat_args!に展開されます)。

手続き的マクロは専用のクレートでしか定義できないため、利用するには外部パッケージとして依存に追加します。derive・属性の形で書くものは!が付かないので、見た目からはマクロと気付きにくい点に注意が必要です。

補足

囲む括弧はどれを使ってもよい

()・[]・{}のどれで囲んでも意味は変わりません。慣習としてvec![1, 2]は角括弧、println!("...")は丸括弧が使われます。ただし文やアイテムの位置で()・[]を使った場合は、末尾にセミコロンが必要です。

依存を追加せずに使えるderive

#[derive(Debug)]のような標準の派生(Clone・Copy・Debug・Default・Eq・Hash・Ord・PartialEq・PartialOrdの9種)はコンパイラと標準ライブラリが提供していて(preludeに含まれます)、依存を追加しなくても構造体や列挙型に付けられます。

展開はコンパイルの早い段階で起きる

マクロの展開はコンパイルの早い段階で行われ、その後の型チェックは展開後のコードに対して行われます。マクロがトレイトの実装を生成できるのはこのためです(関数は実行時に呼ばれるので、同じことはできません)。また展開の際、マクロの内部で使われているローカル変数やループラベルが、呼び出し側の同名のものと取り違えられることはありません(衛生性)。

この辞書が使われているページ

backlinks 11

  1. 辞書Debugトレイト
  2. 辞書derive属性
  3. 辞書Displayトレイト
  4. 辞書never型
  5. 辞書コンソール出力
  6. 辞書パス(Path)
  7. 辞書パニック
  8. 辞書演算子オーバーロード
  9. 辞書標準ライブラリ
  10. 本動かして学ぶRustプログラミング問題集 › 第19章 derive属性と標準トレイト
  11. 本動かして学ぶRustプログラミング問題集 › 第1章 コンソール出力と変数