カレンダーは無事に完成しました!
まとめ記事: Rustマクロ作成チートシート!
こちらの記事は Rust Advent Calendar 2024 2日目の記事です!
本記事は今後の進捗に伴い編集されます!
あらすじ: 夏期講習( Qiita Engineer Festa 2024 )していました
こんにちわ!Rustの記事を投稿しまくりたいnamniumと申します。今年の夏に 100 Exercises To Learn Rust というRustチュートリアルを題材に記事投稿マラソンに取り組み、なんとか 完走 できました。
Rustの基礎から非同期処理に至るまで広い範囲をカバーしたチュートリアルだったため、完走後はRustの新しい概念に出会っても、自信を持って取り組めるようになったのでした...!
...
...
なったはずでした...orz
Rustのマクロ難しすぎる
100 Exercises To Learn Rust では飛ばされていた内容に、マクロ があります。
Rustのマクロは、(仕組みというか思想はかなり違いますが)C言語・C++にあるようなマクロと同様、「コンパイル時に内容を置き換える」メタプログラミングのための仕組みです。
macro_rules! impl_hello {
($type_:ty, $name:expr) => {
impl Hello for $type_ {
fn hello(&self) {
println!("Hello, I'm {}!", $name);
}
}
};
}
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では可変長引数の関数を作ることができない1ためC言語の printf
関数にあたる機能が println!
マクロとして与えられています。こんな感じで、マクロはRustを書いたことがある人なら必ず利用したことがある機能かと思います。 vec!
には特にお世話になっているでしょう。
...が、自分でマクロを書くとなると、通常のRustプログラミングでは求められない知識が盛り沢山です! ドキュメントがないわけではないですが、いざマクロを書こうとした時に、自分にとってすぐに欲しい情報が得られる状況でもなく、そこで夏にチュートリアルに取り組んだように、このアドベントカレンダーを利用して本格的に取り組むことにしました!
カレンダーの予定 & 進捗
(次の内容は本記事投稿当時のものです。)
怠惰な筆者のことだからこうなるだろうなと予想していましたが、遅れています!!!
予定日 | タイトル・リンク | 内容・補足 |
---|---|---|
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
- 宣言的マクロ・手続きマクロ双方について基本的なことが書かれた内容です。主に宣言的マクロの方について網羅されています。
-
proc-macro-workshop
- 手続きマクロについて5つのプロジェクトに取り組むことで、「
derive
マクロ」「属性風マクロ」「関数風マクロ」をマスターできるワークショップです。なかなかハード!
- 手続きマクロについて5つのプロジェクトに取り組むことで、「
Little Bookの方は一通り読んだのですがワークショップの方が途中のためカレンダーが遅れてしまっています。 めげずに頑張っていこうと思います!というわけで、よろしくお願いします!
(ここからは追記した内容です。)
2024年が終わり、2025年もひと月が経った頃、ようやく最後の記事を掲載して完成しました!
以下、全体の目次です!
おまけ・目次を作成したスクリプト
#!/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);
}
}
完走した感想
めちゃくちゃ労力をかけた割に得られたのはグッズのみ...もう満足したので記事投稿マラソンへの参加は控えようと思います...
とはいえこれは筆者個人の執筆スタイルの問題であり、マラソン自体はとても好きな企画です!あまり手間をかけないようにしつつ大量の情報をきれいにまとめていくという作業機会はそうそう得られるものではないので、マクロについて詳細にまとめられたのはマラソンのおかげだと思います。
もしよければ実際に投稿した記事たちも読んでいただけたら幸いです!
-
むしろこの制約もRustの好きな点だったりするのですが... ↩