☃Rustマクロ冬期講習アドベントカレンダーをやります！❄


:::note
カレンダーは無事に完成しました！

まとめ記事: [Rustマクロ作成チートシート！](https://qiita.com/namn1125/items/d601d2cbbf2cc7b5d3dc)
:::

[Rustマクロ冬期講習アドベントカレンダー](https://qiita.com/advent-calendar/2024/rust-macro)

https://qiita.com/advent-calendar/2024/rust-macro

こちらの記事は [Rust Advent Calendar 2024](https://qiita.com/advent-calendar/2024/rust) 2日目の記事です！

本記事は今後の進捗に伴い編集されます！

# あらすじ: 夏期講習( Qiita Engineer Festa 2024 )していました

こんにちわ！Rustの記事を投稿しまくりたいnamniumと申します。今年の夏に [100 Exercises To Learn Rust](https://rust-exercises.com/100-exercises/) というRustチュートリアルを題材に記事投稿マラソンに取り組み、なんとか [完走](https://qiita.com/namn1125/items/2a59f486cce30fd2b889) できました。

Rustの基礎から非同期処理に至るまで広い範囲をカバーしたチュートリアルだったため、完走後はRustの新しい概念に出会っても、自信を持って取り組めるようになったのでした...！

...

...

なった**はず**でした...orz

# Rustのマクロ難しすぎる :persevere:

100 Exercises To Learn Rust では飛ばされていた内容に、[マクロ](https://doc.rust-jp.rs/book-ja/ch19-06-macros.html) があります。

Rustのマクロは、(仕組みというか思想はかなり違いますが)C言語・C++にあるようなマクロと同様、「コンパイル時に内容を置き換える」メタプログラミングのための仕組みです。

```rust:宣言マクロの例
macro_rules! impl_hello {
    ($type_:ty, $name:expr) => {
        impl Hello for $type_ {
            fn hello(&self) {
                println!("Hello, I'm {}!", $name);
            }
        }
    };
}
```

```rust:コンパイル前にこのように展開される(イメージです)
impl_hello!(Hoge, "Hoge");
impl_hello!(Fuga, "Fuga");

// 展開後↓

impl Hello for Hoge {
    fn hello(&self) {
        println!("Hello, I'm {}!", "Hoge");
    }
}

impl Hello for Fuga {
    fn hello(&self) {
        println!("Hello, I'm {}!", "Fuga");
    }
}
```

マクロを使えばこのようにボイラーテンプレートを減らしたり、Rustの標準文法では記述が煩雑になってしまうような処理を簡潔に書くことができます。

例えばRustでは可変長引数の関数を作ることができない[^varlen]ためC言語の `printf` 関数にあたる機能が `println!` マクロとして与えられています。こんな感じで、マクロはRustを書いたことがある人なら必ず利用したことがある機能かと思います。 `vec!` には特にお世話になっているでしょう。

[^varlen]: むしろこの制約もRustの好きな点だったりするのですが...

...が、**自分でマクロを書くとなると、通常のRustプログラミングでは求められない知識が盛り沢山です！** ドキュメントがないわけではないですが、いざマクロを書こうとした時に、自分にとってすぐに欲しい情報が得られる状況でもなく、そこで夏にチュートリアルに取り組んだように、このアドベントカレンダーを利用して本格的に取り組むことにしました！

# カレンダーの予定 & 進捗

(次の内容は本記事投稿当時のものです。)

---

[Rustマクロ冬期講習アドベントカレンダー](https://qiita.com/advent-calendar/2024/rust-macro)

怠惰な筆者のことだからこうなるだろうなと予想していましたが、遅れています！！！

|予定日|タイトル・リンク|内容・補足|
|:----|:---------|:-------|
|1日目予定|Rust宣言マクロ クイックスタート|宣言マクロ作成RTAな内容です。|
|2日目予定|Rust手続きマクロ クイックスタート|手続きマクロ作成RTAな内容です。|
|3日目以降予定|Rustマクロの概要・分類|そもそもRustマクロはどういう体系なのかについて簡単に整理する内容です。|
|4日目以降予定|(タイトル未定)|The Little Bookの内容|
|4日目以降予定|(タイトル未定)|proc-macro-workshopの内容|
|..|(未定)|..|
|14日目|Rustマクロ フラグメント指定子(ident, expr, item, stmt...)チェッカー！|フラグメント指定子が覚えられなさすぎるので、覚えやすくするためのツールを作りました(作っています)。そのツールとフラグメント指定子についての説明です。|
|..|(未定)|..|
|25日目予定|Rustマクロチートシート|アドベントカレンダーに投稿した内容のうち、特に有益と思われる情報を見やすくまとめる予定です。|

筆者オリジナルの内容もいくつか設けたいとは考えていますが、基本的には以下2つのドキュメントの内容を網羅していく所存です！

- [The Little Book of Rust Macros](https://veykril.github.io/tlborm/)
  - 宣言的マクロ・手続きマクロ双方について基本的なことが書かれた内容です。主に宣言的マクロの方について網羅されています。
- [proc-macro-workshop](https://github.com/dtolnay/proc-macro-workshop)
  - 手続きマクロについて5つのプロジェクトに取り組むことで、「`derive`マクロ」「属性風マクロ」「関数風マクロ」をマスターできるワークショップです。なかなかハード！

Little Bookの方は一通り読んだのですがワークショップの方が途中のためカレンダーが遅れてしまっています。 :sweat_smile: めげずに頑張っていこうと思います！というわけで、よろしくお願いします！

---

(ここからは追記した内容です。)

2024年が終わり、2025年もひと月が経った頃、ようやく最後の記事を掲載して完成しました！

[Rustマクロ作成チートシート！](https://qiita.com/namn1125/items/d601d2cbbf2cc7b5d3dc)

https://qiita.com/namn1125/items/d601d2cbbf2cc7b5d3dc

以下、全体の目次です！

|投稿日|タイトル・リンク|
|:---|:-------------|
| 1日目 |[Rust マクロの分類](https://qiita.com/namn1125/items/244803083934bcb7bb51)|
| 2日目 |[Rustマクロの事前知識①「入出力はトークン木」](https://qiita.com/namn1125/items/3288db9acdf2012f78b0)|
| 3日目 |[Rustマクロの事前知識②「出力の掟」](https://qiita.com/namn1125/items/75533eb3fe096c9db0bc)|
| 4日目 |[Rustマクロの事前知識③「マクロのマナー 衛生性(健全性)」](https://qiita.com/namn1125/items/61587afa264107ba7567)|
| 5日目 |[Rustマクロの事前知識④ 「聴診器 cargo-expand」](https://qiita.com/namn1125/items/2ad7ab31085b99b586e7)|
| 6日目 |[Rust 宣言マクロ( macro_rules! )の勘所](https://qiita.com/namn1125/items/63756af0d3cecdaa02cd)|
| 7日目 |[Rustマクロ フラグメント指定子](https://qiita.com/namn1125/items/303170b563030fd207cc)|
| 8日目 |[Rust宣言マクロ 再帰テクニック](https://qiita.com/namn1125/items/3439dcab18e6d6d86554)|
| 9日目 |[Rust 宣言マクロ小ネタ集【光編① マクロを作る時に便利なマクロ】](https://qiita.com/namn1125/items/df4ccd6e0c16bd94fed0)|
| 10日目 |[Rust 宣言マクロ小ネタ集【光編② メタ変数式】](https://qiita.com/namn1125/items/1e95118aaf7019a4ecd0)|
| 11日目 |[Rust 宣言マクロ小ネタ集【コールバック・マクロの展開結果を別なマクロに渡す方法】](https://qiita.com/namn1125/items/daf97caab370d610a293)|
| 12日目 |[Rust 宣言マクロ小ネタ集【闇編 宣言マクロ・マッチの限界】](https://qiita.com/namn1125/items/9db25d9db302a871ff03)|
| 13日目 |[Rust 宣言マクロのスコープ・インポート・エクスポート](https://qiita.com/namn1125/items/5cfef3e6679045f93a7e)|
| 14日目 |[Rustマクロ フラグメント指定子(ident, expr, item, stmt...)なんもわからん！となったので判別器作った](https://qiita.com/namn1125/items/93461ddf93277b79a96f)|
| 15日目 |[Rust 手続きマクロ(proc-macro)の勘所](https://qiita.com/namn1125/items/06e65d1313ca50a1eb5d)|
| 16日目 |[Rust 関数風マクロRTA (最小構成の関数風マクロ)](https://qiita.com/namn1125/items/fc5aa71690fcb5109cf0)|
| 17日目 |[Rust 属性風マクロRTA (最小構成の属性風マクロ)](https://qiita.com/namn1125/items/b7d9374e3d6d3f64ab65)|
| 18日目 |[Rust deriveマクロRTA (最小構成のderiveマクロ)](https://qiita.com/namn1125/items/b05ad80d324afcd2ed91)|
| 19日目 |[Rust 関数風マクロを軽くハンズオン](https://qiita.com/namn1125/items/648e915cad62f21656c0)|
| 20日目 |[Rust 属性風マクロを軽くハンズオン](https://qiita.com/namn1125/items/28d2e95090ddf7fbc7e0)|
| 21日目 |[Rust deriveマクロを軽くハンズオン](https://qiita.com/namn1125/items/b55fb65f423cca0bef49)|
| 22日目 |[Rust手続きマクロ エラーハンドリング手法](https://qiita.com/namn1125/items/f71f204264aa7cd0a6ab)|
| 23日目 |[Rust 手続きマクロ 第四の神器 darling](https://qiita.com/namn1125/items/d136a51184d044330f00)|
| 24日目 |[【手続きマクロだけではない】synクレートを活用して便利ツールを作った話【Rust】](https://qiita.com/namn1125/items/c7659e863e8b633668b4)|
| 25日目 |[Rustマクロ作成チートシート！](https://qiita.com/namn1125/items/d601d2cbbf2cc7b5d3dc)|

<details><summary>おまけ・目次を作成したスクリプト</summary>

```rust:Rust
#!/usr/bin/env -S cargo +nightly -q -Zscript run --release --manifest-path
---
[dependencies]
glob = "0.3.2"
regex = "1.11.1"
---

fn main() {
    let title_re = regex::Regex::new(r"title: (.*?)\n").unwrap();
    let id_re = regex::Regex::new(r"id: (.*?)\n").unwrap();
    let day_re = regex::Regex::new(r"(\d+)日目").unwrap();

    let mut res_vec = Vec::new();

    for entry in glob::glob("./**/macro_adv_*.md").unwrap() {
        let Ok(path) = entry else {
            println!("Failed to read path");
            continue;
        };

        let content = std::fs::read_to_string(&path).unwrap();

        let Some(title) = title_re.captures(&content).and_then(|cap| cap.get(1)).map(|m| m.as_str()) else {
            println!("Failed to read title: {}", path.display());
            continue;
        };

        let Some(id) = id_re.captures(&content).and_then(|cap| cap.get(1)).map(|m| m.as_str()) else {
            println!("Failed to read id: {}", path.display());
            continue;
        };

        let Some(day): Option<usize> = day_re.captures(&content).and_then(|cap| cap.get(1)).map(|m| m.as_str().parse().unwrap()) else {
            println!("Failed to read day: {}", path.display());
            continue;
        };
        
        let line = format!("| {}日目 |[{}](https://qiita.com/namn1125/items/{})|", day, title, id);

        res_vec.push((day, line));
    }

    res_vec.sort_by_key(|(day, _)| *day);

    for (_, line) in res_vec {
        println!("{}", line);
    }
}
```
</details>

# 完走した感想

めちゃくちゃ労力をかけた割に得られたのはグッズのみ...もう満足したので記事投稿マラソンへの参加は控えようと思います... :sweat_smile:

とはいえこれは筆者個人の執筆スタイルの問題であり、マラソン自体はとても好きな企画です！あまり手間をかけないようにしつつ大量の情報をきれいにまとめていくという作業機会はそうそう得られるものではないので、マクロについて詳細にまとめられたのはマラソンのおかげだと思います。

もしよければ実際に投稿した記事たちも読んでいただけたら幸いです！