読んだ本
著者: Sam Van Overmeire
Rustacean stationというpodcastで紹介されていて、おもしろそうだったので読んでみました。
1 Going meta
hygieneやcompile時にチェックされる等、Rustのmacroの特徴について。
Procedural macroはderive macro, attribute macro, function like macroに分類されます。これらにどういった違いがあるのか自分は曖昧に理解していたのですが、deriveはcodeの追加のみ、attributeとfunction likeはcodeの変更や削除までできるという違いがあることがわかりました。
2 Declarative macros
Declarative macroについての章です。
Declarative macroの基本的な書き方から、hygieneまで解説があります。
Usecaseとして、newtype pattern実装時のboilerplateの記述等が紹介されます。
また、trace_macros!
やlog_syntax!
によるdebug方法の解説もあります。
log_syntax!
は使ったことがなかったです。compile時にdebugできるのでcargo check
でmacroをdebugできます。
その他、簡単なDSLや関数をcomposeする例が載っています。
Real world usecaseとしてlazy_static!
の実装の解説もあります。
3 A "Hello, World" procedural macro
3章からはprocedural macroの話になります。
まずderive macroの説明から始まります。 projectのsetupの仕方からmacroの処理の概要、syn
やquote
の基本的な使い方が解説されます。
以下のような#[derive(Hello)]
の実装を1行づつ説明してくれます。
use TokenStream;
use quote;
use DeriveInput;
use Hello;
本書を試していて気づいたのですが、cargo expand
はmain.rsだけでなく、moduleのpathも指定できて、cargo expand path/to/module/hello
のように実行すると、crate::path::to::module::hello
の内容が展開できました。
今まで、main.rs等にcopyしていたのですが、これからは気になったコードをすぐにexpandできそうです。
4 Making fields public with attribute macros
本章では、付与された型のfieldをpublicにする#[public]
attribute macroを実装していきます。
が以下のように変換されます。
この処理を実装するには、structのfieldをparseする必要があります。
この例を通じて、quote::ToTokens
やsyn::parse::Parse
の仕組みを学べます。proc_macro2::TokenStream
も登場します。
fieldを加工できるようになるといよいよできることが広がってきておもしろくなってきます。
また、real worldのusecaseとして、dtolnay先生のno-panicも紹介されます。
章末のexercisesまでやると、各種structとenumの対応まで実装できます。
5 Hiding information and creating mini-DLSs with function-like macros
5章はfoo!()
のようなfunction like macroについてです。
function like macroはattribute macroのようにinputのTokenStreamを置き換えられます。
attribute macroとの違いは、macroの入力として、rustのcodeに限らず任意の入力を渡せる点です。
この特性から、sqlx
やyew
では、SQLやhtmlを入力にとれるmacroが提供されています。
本章では、2章で実装した、compose macroのprodedural版を実装します。
use compose;
6 Testing a builder macro
6章では、#[derive(Builder)]
macroによるbuilder patternの実装を通して、macroのtestについて学びます。また、proc-macro2
を利用して、procedural macroの実装を通常のlib crateに移譲する方法についても説明されます。
他のprocedural macroについてのリソースについてdtolnay先生のproc-macro-workshopやjon gjengset先生のProcedural Macros in Rust 動画が紹介されていました。
7 From panic to result: Error handling
本章では、以下のようなpanic!()
する関数をResultを返す関数に変換する#[panic_to_result]
を通して、関数の変換とエラーハンドリングについて学びます。
変換時のエラー、例えばpanic!()
にメッセージがない場合をユーザにわかりやすく伝える方法が解説されます。proc-macro-errorは知らなかったので参考になりました。
8 Builder with attributes
8章では、attributesを扱う方法について学びます。 以前のBuilder macroでrenameを扱えるようにします。
attributeにも、#[rename(foo)]
であったり、#[rename(key=value)]
と書き方が複数あり、それぞれsyn上での表現が異なります。
また、今のBuilderの実装では、ユーザが適切にbuilderのmethodを呼び出しかどうかをruntime時にチェックしています。
これをcompile時にチェックできるようにいわゆるtype state patternを実装します。 attribute macroの場合、他のattributeを保持するかどうかも判断する必要があり、#[allow()]
等の他のattributeを保持するか否かを判定する必要があるのは知らなかったので参考になりました。
9 Writing an infrastructure DSL
9章では、function like macroでDSLを作っていきます。
具体的には以下のようにAWSのresourceを宣言するterraformのようなInfrastructure as Code(IaC) macroを作ります。
iac!
この実装の中で、syn::custom_keyword!
やsyn::parenthesized
の使い方も紹介されます。 From the real worldではdeclaratve macroとprocedural macroを組み合わせて使われている例が紹介されていました。
10 Macros and the outside world
最後の10章はcompile時にyaml fileからConfig
structを生成するconfig!
を実装します。
本章ではproc macro crateのfeature制御やdocumentについての解説もあります。
最後にまとめとして、#[tokio::main]
のsrcの解説があります。
また、本書で紹介しきれなかったsynのFold
traitの解説記事やproc macro実装時に利用できるcrateが紹介されています。
まとめ
Procedural macroだけでほぼ1冊書かれており、実際にcodeを書きながら少しずつ改良していくのでとても楽しく読めました。
章末のexerciseの答えもappendixに載っていてありがたいです。
今までprocedural macroの実装を読んでいなかったのですが、これからは色々なlibの実装を読んでみようと思えるようになりました。