Pro Yearly is on sale from $80 to $50! »

Rustの全マクロ種別が分かったつもりになれる話! / rust-all-kinds-of-macro

5c097a7b75c8802b073774faa7276503?s=47 optim
July 28, 2020

Rustの全マクロ種別が分かったつもりになれる話! / rust-all-kinds-of-macro

Rustのマクロの書き方を一通りご紹介します。macro_rules!で定義する一般的な宣言マクロはもちろん、#[derive]で使う自動導出マクロや属性マクロなどの手続きマクロも説明します。これであなたも黒魔術師!

2020/07/28 OPTiM TECH NIGHT|Rustの全マクロ種別が分かったつもりになれる話!
https://optim.connpass.com/event/180982/

5c097a7b75c8802b073774faa7276503?s=128

optim

July 28, 2020
Tweet

Transcript

  1. Copyright © OPTiM Corp. All Right Reserved. 1 OPTiM R&Dチーム

    齋藤 OPTiM TECH NIGHT OVER ZOOM 2020/07/28 Rustの全マクロ種別が 分かったつもりになれる話!
  2. Copyright © OPTiM Corp. All Right Reserved. 2  齋藤

    OPTiM R&Dチーム サブマネージャー 2017/04 新卒入社  最近の仕事 • Optimal Biz Telework(iOS)  Rust歴 • 1.0(ベータ?)~  最近(?)ハマってるゲーム • Beat Saber
  3. Copyright © OPTiM Corp. All Right Reserved. 3  Rustの全マクロ種別が分かったつもりになれる話!

     マクロとは  宣言マクロ  手続きマクロ • 関数式手続きマクロ • 自動導出マクロ • 属性マクロ  さいごに 今回紹介する内容
  4. Copyright © OPTiM Corp. All Right Reserved. 4  日本語訳は独自の部分があり、広く使われている訳語ではない可能性があります

     Rust 1.45以降での動作を想定しており、それ以前のバージョンでは動かない可能性があります はじめに
  5. Copyright © OPTiM Corp. All Right Reserved. 5 Copyright ©

    OPTiM Corp. All Right Reserved. マクロについて
  6. Copyright © OPTiM Corp. All Right Reserved. 6  「マクロとは、関連する複数の操作や手順、命令などを一つにまとめ、

    必要に応じて呼び出すことができるようにする機能のこと。」 • IT用語辞典 e-Wordsより  関数は処理をまとめるのに対し、マクロはコードそのものをまとめる  つまり、コードでコードを生成する  メタプログラミングとも呼ばれる  C言語ではプリプロセッサと呼ばれるほぼ単純なテキスト処理機構によって マクロが実現される  マクロの展開はその場にテキストが挿入されることを意味する  マクロの中で変数を宣言すると、その場で変数を宣言したことになってしまう  Rustにおいては言語機能を拡張するもの • マクロが展開されても識別子が衝突しない • マクロの中で変数を宣言しても、呼び出し側からは見えない • マクロで受け取った識別子であれば呼び出し側から見える • これを衛生的マクロや健全なマクロ(Hygienic macros)と呼ぶ マクロってなんだ
  7. Copyright © OPTiM Corp. All Right Reserved. 7  lazy_static!、#[derive(...)]、#[async_trait]はすべてマクロ

     lazy_static!は遅延初期化するグローバル変数を 宣言するコードを生成する  #[derive(Serialize, Deserialize)]は Serializeトレイト、Deserializeトレイトを実装する コードを生成する  #[async_trait]はトレイト宣言の戻り値や 実装を書き換える Rustのマクロは言語を拡張する
  8. Copyright © OPTiM Corp. All Right Reserved. 8  Rustマクロには、使用方法で分けると3種類、定義方法で分けると4種類ある

     使用方法 1. foo!()、foo![]、foo!{} • 関数式マクロ(Function-like macros) 2. #[derive(Foo)] struct Bar {} • 自動導出マクロ (Derive macros) 3. #[foo] fn baz() {} • 属性マクロ(Attribute macros)  定義方法 1. macro_rules! foo {} • 宣言マクロ(Declarative macros) 2. #[proc_macro] fn bar() {} • 関数式手続きマクロ(Function-like procedural macros) 3. #[proc_macro_derive(Trait, attributes(helper))] fn baz() {} • 自動導出マクロ(Derive macros) 4. #[proc_macro_attribute] fn qux() {} • 属性マクロ(Attribute macros) Rustのマクロの種別について 使用方法 宣言マクロでの定義 手続きマクロでの定義 foo!() macro_rules! #[proc_macro] fn #[derive(Foo)] #[proc_macro_derive] fn #[foo] #[proc_macro_attribute] fn 2~4をまとめて 手続きマクロ(Procedural macros)と呼ぶ
  9. Copyright © OPTiM Corp. All Right Reserved. 9  マクロはコンパイル時に展開されるため、どのように展開されたかが分からず、デバッグしづらい

     →cargo-expandを使うと(マクロ以外も含めて)展開されたコードが分かる  インストール •  使い方 • • 子モジュールなども含めてすべて展開されるのでかなり厄介 • • 指定されたアイテム(この場合Trait)のみが展開される マクロのお供
  10. Copyright © OPTiM Corp. All Right Reserved. 10 Copyright ©

    OPTiM Corp. All Right Reserved. 宣言マクロ
  11. Copyright © OPTiM Corp. All Right Reserved. 11  宣言マクロとは、コード上ですべてを宣言するマクロである

    • 括弧の中(foo!(ここ))に書いた文字列を元に、呼び出し全体を書き換えるマクロ  macro_rules! makro { ... }で定義する • 定義方法は後ほど • makro!()として呼び出す • 括弧は丸括弧(())でなく、角括弧([])や波括弧({})でも良い • 括弧の種類による違いはほぼ無く、波括弧の場合はセミコロンを省略できる(式ではなく文として扱われる)程度  名前解決がかなり面倒くさい • 関数などの名前解決とは違うルールで動き、使う場所よりも先に定義されていないとならない • 同一クレートの場合が特に厄介で、モジュールとしても先に定義されている必要があるため、 マクロはmacros.rsなどにまとめて定義しておき、 lib.rsなどではファイルの先頭で#[macro_use] mod macros;しておくのが吉  別クレートで使う時、Rust 2018 editionからは#[macro_use] extern crate hoge;が必須ではなくなった • もはや無いとは思うが、Rust 2015にしか対応していないクレートは#[macro_use]でないと使えない 宣言マクロとは
  12. Copyright © OPTiM Corp. All Right Reserved. 12  macro_rules!に後続してマクロ名を書き、そのあとにマクロのパターンを書く

    1. 定義する際はオーバーロードのようなものが使える 2. パターンには正規表現のようなものが使える 3. 別モジュールや別クレートから使えるようにするには #[macro_export]を付ける必要がある  右の変数を宣言するマクロの例では、 型を省略したパターンと明示したパターンを定義している • パターンは上から順にマッチするか試され、 最初にマッチしたパターンが使われる • →全く同じパターンが定義されていてもエラーにならない • マクロ呼び出しの中で使ったxやyが呼び出し側でも使えている • 呼び出し側から渡した識別子なので衛生性が保たれていると言える  矢印=>に続いて丸括弧(())、角括弧([])、 波括弧({})のいずれかの中に書き換え後のコードを書く • マクロからマクロを呼び出すことも出来る 宣言マクロの定義方法
  13. Copyright © OPTiM Corp. All Right Reserved. 13  パターンにはただの文字列に加えて断片(fragment)を指定出来る

    • 断片は$name:kindのような形式で指定し、kindに指定した形式ごとにマッチする種類が異なる • 代表的なkindの形式 • ident:識別子 • expr:式 • ty:型 • stmt:セミコロンを除く文 • tt:任意のトークン • 形式により、続くトークンに制限がある • 例えばexprのあとには「=>」か「,」、「;」しか続けられない • 例えば$name:expr[foo]のようなパターンを定義したとき、 makro!(hoge[foo][foo])に対してどこまでマッチするかが曖昧なため  また、パターンを$()*や$()+などで囲うと、 パターンを反復できる • $(pattern)*ではパターンは0個以上でマッチする • $(pattern)+ではパターンは1個以上でマッチする • $(pattern)?ではパターンが0個か1個でマッチする  右の例では、$nameは識別子に、や$valは式にマッチする • 正規表現で書くと/((¥w+) = (.+?);)+/みたいな感じ パターン
  14. Copyright © OPTiM Corp. All Right Reserved. 14  矢印のあとに続けて書いた書き換え後のコードでは、マッチした断片($nameなど)を使うことが出来る

    • また、パターンの中で反復指定($(pattern)+など)を使った場合は、 書き換え後のコードでも反復指定を断片と共に使う  マクロを定義したクレートに含まれるアイテムを使う場合、 $crate::hogeのようにしてアクセスする必要がある • マクロが展開される場所からはアクセス出来ないため • $crateはマクロが定義されたクレートへのパスに展開される • crate($抜き)を使うと使用場所のクレートになってしまうので注意  右の例では、x = 100というコードと y = 10u8というコードにマッチしている • マッチした結果、それぞれ let mut x = 100;及び let mut y = 10u8;というコードに書き換えられている 書き換え後のコード
  15. Copyright © OPTiM Corp. All Right Reserved. 15  複数パターンを反復するようなマクロの場合、

    まずはどれでもマッチするマクロを書き、 その中で分解するという手法を取る • 単体でマクロがマッチすると誤作動のもとなので、 アットマーク(@)に続けて適当な文字列を入れて マッチしないようにするのが定石 • ここではシンプルにするために1つのマクロの中に すべてを定義しているが、アットマークのパターンを 別マクロとして定義することが多い  右の例の場合、型を省略するパターン、 明示するパターンを反復している • まずは3番目のパターンで全体をマッチさせ、 その中で1つずつ、省略・明示のどちらかのパターンで マッチさせるようにしている 複雑なマクロ
  16. Copyright © OPTiM Corp. All Right Reserved. 16 複雑なマクロ ステップ2:1番目にマッチ

    ステップ1:全体(3番目)にマッチ ステップ3:2番目にマッチ
  17. Copyright © OPTiM Corp. All Right Reserved. 17  再帰を多用するマクロは再帰制限に引っ掛かる場合がある

    • stdweb::js!などが引っ掛かる • デフォルト値は128だが、lib.rs・main.rsに#![recursion_limit = “256“]のように書くことで制限を緩和できる • 手続きマクロならこの制限に引っ掛かることはないため、再帰マクロを定義する際は手続きマクロでの実装も考えると良い  最新情報はMacros By Exampleを見るのがオススメ • https://doc.rust-lang.org/reference/macros-by-example.html  知名度低そうな断片形式 • $(hoge)? • hogeというトークンが0個か1個 • Rust 1.32で入り、正規表現っぽさが増した • $xxx:lifetime • $xxxはライフタイム('fooなど)にマッチする • $xxx:vis • $xxxは可視性(pubやpub(crate)など)にマッチする • $xxx:pat • $xxxはパターン(matchなどの条件として使えるもの)にマッチするs 宣言マクロの追加情報
  18. Copyright © OPTiM Corp. All Right Reserved. 18 Copyright ©

    OPTiM Corp. All Right Reserved. 手続きマクロ
  19. Copyright © OPTiM Corp. All Right Reserved. 19  マクロをユーザー定義の関数(手続き、procedure)によって解釈する仕組み

    • 入力がトークン列、出力もトークン列である関数として定義する • 感覚的にはソースコードを入力してソースコードを出力する関数  トークン列のパース・意味解析から展開までを関数でやるため実装は面倒だが、その分柔軟性が高い • 柔軟性が高いというかなんでも出来る。マクロの中でファイルの読み書きからネットワークのやり取りまで出来る  例: 1. #[derive(Serialize, Deserialize)]と書くだけで構造体をシリアライズ・デシリアライズ出来るserdeクレート 2. indoc!("")のようにして複数行文字列を書くと、よしなに最初のインデント部分を消してくれるindocクレート 3. yew! { <p>{ self.value }</p> }のようにReactのJSXっぽい記法でWebアプリが書けるyewクレート 4. cpp! { cv::imread("hoge.png") }のようにRustの中にC++を書けるcppクレート 5. 関数に属性を付けるだけでHTTPサーバーのエンドポイントが定義できるRocketクレート 6. トレイトに#[async_trait]属性を付けることで、トレイトメソッドをasync fn化できるasync-traitクレート 手続きマクロとは
  20. Copyright © OPTiM Corp. All Right Reserved. 20  手続きマクロには3種類ある

     関数式マクロ • 宣言マクロと同じように使える手続きマクロ • 以前は使える場所が限られていたが、Rust 1.45からは宣言マクロと同じく様々な場所で扱えるようになり、 使える場所が大きく広がった • 例:yew! { <p>{ self.value }</p> }  自動導出マクロ • derive属性で使われるマクロで、基本的にトレイトの実装に使われる • 例:#[derive(Serialize, Deserialize)]  属性マクロ • 関数や構造体などあらゆるアイテムに付け、それらアイテムを書き換えるマクロ • 一番黒魔術してるマクロ • 例:#[async_trait] trait Trait {} 手続きマクロとは
  21. Copyright © OPTiM Corp. All Right Reserved. 21  Cargo.tomlに[lib]

    proc-macro = trueを追加する • この設定をしたクレートは手続きマクロ以外を公開できないため、 一般的に手続きマクロは別クレート(*-macrosや*-implなど)として 分離して定義され、本体のクレートから再エクスポートされる  手続きマクロ本体の関数は以下の条件を満たす必要がある • クレートのルート(lib.rs)に定義すること • pubで定義すること • fn(proc_macro::TokenStream) -> proc_macro::TokenStreamとして定義すること • 属性マクロの場合はfn(proc_macro::TokenStream, proc_macro::TokenStream) -> proc_macro::TokenStream • #[proc_macro]など、定義するマクロの種類に応じて属性を付けること →最低限のパースだけlib.rsで行い、あとはモジュールに分割した展開用の関数で処理するのが分かりやすい 手続きマクロを作るには
  22. Copyright © OPTiM Corp. All Right Reserved. 22  手続きマクロだけを定義しているクレートは分離する必要がないため、以下には示していない

    有名なクレートでのマクロの分離の仕方 クレート名 代表的なアイテム 実体のあるクレート futures select!/join! futures-macro indoc indoc! indoc-impl yew html! yew-macro serde Serialize/Deserialize serde_derive structopt StructOpt structopt-derive failure Error failure_derive thiserror Error thiserror-impl tokio #[tokio::main] tokio-macros async-std #[async_std::main] async-attributes actix-rt #[actix_rt::main] actix-macros pin-project #[pin_project] pin-project-internal wasm-bindgen #[wasm_bindgen] wasm-bindgen-macro pyo3 #[pyclass]/#[pymodule] pyo3cls
  23. Copyright © OPTiM Corp. All Right Reserved. 23  手続きマクロを使う時、これらのクレートを使わない場合は地獄を見ることになる

     proc_macro • proc-macro = trueしたら使える組み込みのクレート。逆に言えば手続きマクロ以外で使うことは(実質)出来ない • 手続きマクロに渡されるTokenStreamやSpanなど、手続きマクロで必須の構造体が定義されている • 基本的にはproc_macro2を通じて使うのであまり意識することはない  proc_macro2 • proc_macroと同じ機能を、手続きマクロクレート以外でも使えるようにするためのクレート • これによってテストが書けるようになる  syn • トークン列を構文木としてパースするためのクレート • Rust標準の構文をパースするための構造体も定義されている  quote • synでパースした結果をRustコードに変換し、トークン列に戻すためのクレート • quote!マクロで入力を切り貼りして戻り値にする 手続きマクロにほぼ必須のクレート
  24. Copyright © OPTiM Corp. All Right Reserved. 24 大雑把な処理の流れ ソースコード

    トークン列 (引数) 構文木 内部表現 トークン列 (戻り値) トークン列に分解 syn::parse_macro_input!: 構文木にパース quote::quote!: ソース(トークン列)に展開 解 析 コンパイル結果 コンパイル続行 コンパイラ 手続きマクロ
  25. Copyright © OPTiM Corp. All Right Reserved. 25  手続きマクロはlib.rsにしか定義できないため、lib.rsには最低限の処理だけ書き、

    本体は別モジュールに書くことが多い  lib.rsの関数は「入力を変換して返し、変換できなかったらコンパイルエラーを返す」もの • body::bodyはsyn::Result<proc_macro2::TokenStream>を返す • syn::Result<T>はResult<T, syn::Error>を表す(=Result<proc_macro2::TokenStream, syn::Error>) • syn::Error::to_compile_errorはエラーを表すトークン列(proc_macro2::TokenStream)を返す • proc_macro2::TokenStream::intoでproc_macro::TokenStreamに変換する 手続きマクロの頻出パターン
  26. Copyright © OPTiM Corp. All Right Reserved. 26  proc_macro2::Span

    • 元々のソースコードのトークン位置とマクロ展開後のトークン位置を紐付けるための構造体 • 複数のSpanを結合することも出来る  syn::Error • コンパイルエラーを表す構造体で、エラーメッセージはSpanを元に波線が引かれる • Error::new<T: Display>(span: Span, message: T) -> Self • 特定のSpan上でのエラーを生成する • Error::new_spanned<T: ToTokens, U: Display>(tokens: T, message: U) -> Self • 本当はError::newだけにしたいが、安定版Rustの制限上こちらの方がエラーメッセージが親切とのこと • Error::to_compile_error(&self) -> TokenStream • Errorを、コンパイルエラーを表すTokenStreamに変換する  syn::parse_macro_input! • syn::parse_macro_input!(input as Input)として使う • inputはproc_macro::TokenStream型、Inputはsyn::parse::Parseトレイトを実装した型 • 手続きマクロの関数の入力をパースするためのマクロで、パース出来なければエラーを返す 手続きマクロで知っておくべきアイテム
  27. Copyright © OPTiM Corp. All Right Reserved. 27  quote::quote!

    • Rustコードの中にsynの構造体やTokenStreamなどを埋め込むのに便利なマクロ • マクロの中で#identの形で識別子を書くと、 その変数が表すトークンを埋め込める • 宣言マクロのように反復パターン(#(#ident,)*)が使える • この時のidentはイテレータ  quote::quote_spanned! • quote::quote!マクロと同じだが、トークン列全体が、指定されたSpanに位置するものとする 手続きマクロで知っておくべきアイテム
  28. Copyright © OPTiM Corp. All Right Reserved. 28 Copyright ©

    OPTiM Corp. All Right Reserved. 関数式手続きマクロ
  29. Copyright © OPTiM Corp. All Right Reserved. 29  宣言マクロのマッチングとコード書き換えを関数で出来るようにしたもの

    • マッチング対象(makro!(ここ))は、括弧(丸・角・波)の対応が取れている必要があるが、それ以外の制限は無い • makro!()として呼び出す • 括弧は丸括弧(())でなく、角括弧([])や波括弧({})でも良い • 括弧の種類による違いはほぼ無く、波括弧の場合はセミコロンを省略できる(式ではなく文として扱われる)程度  手続きマクロ定義時に付ける属性は#[proc_macro]  宣言マクロと異なり、再帰制限に引っ掛かることはない • 宣言マクロはコンパイラが実行するものだったためこの制限があったが、手続きマクロはプログラムとして実行される ため、 再帰で引っ掛かるとするとスタックオーバーフロー 関数式手続きマクロについて
  30. Copyright © OPTiM Corp. All Right Reserved. 30  Rust

    1.30(2018/10/25)で属性マクロと共に導入された • ただしこの時点で使えるのは関数の生成などに限られ、値を返すような式として扱うことは出来なかった  Rust 1.45(2020/07/16)で式としても扱えるようになった 関数式手続きマクロの歴史
  31. Copyright © OPTiM Corp. All Right Reserved. 31  関数式手続きマクロには#[proc_macro]を指定する

    • 関数名がそのままマクロ名となる  関数にはマクロ呼び出しに指定されたトークン列が渡される • 呼び出しにはあらゆるトークンが入れられるため、自動導出マクロの様な特殊な構造体はない 関数式手続きマクロの書き方
  32. Copyright © OPTiM Corp. All Right Reserved. 32  サンプル:コマンドの出力結果を文字列リテラルとして展開するマクロを定義する

    関数式手続きマクロのサンプル
  33. Copyright © OPTiM Corp. All Right Reserved. 33 1. マクロの引数をパースし、

    コマンドの情報を得る 2. 実際にコマンドを実行し、標準出力を得る 3. 標準出力を文字列リテラルとして、 トークン列を返す 関数式手続きマクロのサンプル ① ② ③ ①
  34. Copyright © OPTiM Corp. All Right Reserved. 34 関数式手続きマクロのサンプル –

    構文木の定義
  35. Copyright © OPTiM Corp. All Right Reserved. 35 関数式手続きマクロのサンプル –

    構文木の解析
  36. Copyright © OPTiM Corp. All Right Reserved. 36  実際にコードを展開してみるとこうなる

    関数式手続きマクロのサンプル
  37. Copyright © OPTiM Corp. All Right Reserved. 37 Copyright ©

    OPTiM Corp. All Right Reserved. 自動導出マクロ
  38. Copyright © OPTiM Corp. All Right Reserved. 38  よくserdeでお世話になるやつで、基本的にはトレイトの実装の際に使う

    • #[derive(Hoge)]としたとき、Hogeトレイトを実装する意味を持つ • マクロの名前空間とトレイトの名前空間は別であるため、既存のトレイトを上書きするようなインポートをしても問題ない • この仕組みにより、thiserror::Errorは#[derive(Error)]と書ける (std::error::Errorトレイトと同時にインポート出来る) • ただし出力するトークン列は任意なので#[derive]に指定した名前と別のトレイトを実装できるし、 そもそもトレイトを実装せず全く別なトークン列を出力することも出来る  #[derive]が付けられるのは構造体(struct)、列挙体(enum)、共用体(union)のみ • もちろんマクロ実装側で構造体だけ許容する、みたいなことも出来る  追加でヘルパー属性と呼ばれる疑似属性を定義できる • 構造体そのものや各フィールドに付けることが出来る、deriveを付けたときだけ使える属性 • serdeの#[serde]のように設定に使われる • 自動導出マクロを定義する際には使用するヘルパー属性の名前を任意の種類数指定できる • ヘルパー属性は自動導出マクロが処理されたあと削除される 自動導出マクロについて
  39. Copyright © OPTiM Corp. All Right Reserved. 39  昔の自動導出マクロはヘルパー属性にキツイ制限があり、

    #[serde(key = “value”)]のように値には文字列でしか渡せなかった • 現在は割と何でも受け入れられるようになっている  #[derive]が導入されたのはRust 1.15(2017/02/02)で、 この制限が緩和されたのがRust 1.30(2018/10/25)  当時のこの制限のため、serdeはあらゆる値を文字列として与える設計になっている • 例えばデフォルト値を与えるにも#[serde(default = "default_fn")]のように関数へのパスを文字列として指定す ることになっている • 本当は#[serde(default = 0)]とか#[serde(default = default_fn())]とか指定したい  そういう経緯があるのでserdeが使いにくくても怒らないでください... • serde 2.0はよ 自動導出マクロの歴史
  40. Copyright © OPTiM Corp. All Right Reserved. 40  手続きマクロには#[proc_macro_derive(Hoge)]を指定する

    • ヘルパ属性を使う場合は#[proc_macro_derive(Hoge, attributes(fuga, piyo))]のように指定する • 手続きマクロの関数名は使われないので何でも良い • 良くderive_hogeの様に定義される  入力のトークン列はsyn::DeriveInputとして定義されている • ジェネリクス指定や各フィールドの属性など、宣言時のトークン列が定義されている • dataフィールドに構造体・列挙体・共用体の宣言が入っている • implのコードを生成するときに必要なのでidentやgenericsフィールドも重要 自動導出マクロの書き方
  41. Copyright © OPTiM Corp. All Right Reserved. 41  サンプル:Defaultトレイトを楽に実装できるRakuDefaultマクロを定義する

    • Defaultはデフォルトでderiveでき、マクロの名前がDefaultだと逆に混乱するので別名にしている  制限:今回は属性の解析にsyn::Metaを使うため、#[raku_default(vec![])]の様な書き方が出来ない • 自分でパースすればやれる 自動導出マクロのサンプル 前編
  42. Copyright © OPTiM Corp. All Right Reserved. 42 1. トークン列をパースし、syn::DeriveInput::dataを見て、

    #[derive(RakuDefault)]が付けられたアイテムが構造体以外であればエラーを返す 2. 実装コード用に、構造体の識別子名と分解されたジェネリクスのトークンを取得 3. コードを書いて トークン列を返す • 識別子が上書きされていても動くよう、 Defaultトレイトは絶対パスで指定 自動導出マクロのサンプル 前編 ① ② ③ ①
  43. Copyright © OPTiM Corp. All Right Reserved. 43  これまでのコードを使い、テストコードを書いて展開するとこの様になる

    自動導出マクロのサンプル 前編
  44. Copyright © OPTiM Corp. All Right Reserved. 44  フィールドに#[raku_default(value)]が指定されていたら、

    それを使ってDefaultの実装に挿入するコードを作る 自動導出マクロのサンプル 後編 イメージ
  45. Copyright © OPTiM Corp. All Right Reserved. 45 自動導出マクロのサンプル 後編

  46. Copyright © OPTiM Corp. All Right Reserved. 46  実際にコードを展開してみるとこうなる

    自動導出マクロのサンプル 後編
  47. Copyright © OPTiM Corp. All Right Reserved. 47 Copyright ©

    OPTiM Corp. All Right Reserved. 属性マクロ
  48. Copyright © OPTiM Corp. All Right Reserved. 48  関数やトレイトなど各種アイテムに付けられ、そのアイテム全体を書き換えることが出来る

     属性を付けられたアイテムは構文としてパース出来れば良く、意味解析はマクロに渡される段階では行われ ない • つまり、マクロで処理した結果、意味が通るような文に書き換わっていれば良い • 例えばトレイトでのasync fnは現在のRustでは使うことは出来ないが、 async-traitではこの仕組みを使ってasync fnを使えるようにしている • 最近のRustのバージョンアップで構文としての制限を緩和する更新がよく入るのはこのため 属性マクロについて
  49. Copyright © OPTiM Corp. All Right Reserved. 49  Rust

    1.30(2018/10/25)で関数式手続きマクロと共に導入された 属性マクロの歴史
  50. Copyright © OPTiM Corp. All Right Reserved. 50  属性マクロには#[proc_macro_attribute]を指定する

    • 関数名がそのまま属性名となる  関数には「属性としてのトークン列」と「属性を付けたアイテムのトークン列」の2つが渡される • 属性はありとあらゆるアイテムに付けられるため、自動導出マクロの様な特殊な構造体(syn::DeriveInput)は無い 属性マクロの書き方
  51. Copyright © OPTiM Corp. All Right Reserved. 51  サンプル:(マクロ内を除く)すべての文字列リテラルを(´・ω・`)に書き換える属性マクロを定義する

    属性マクロのサンプル
  52. Copyright © OPTiM Corp. All Right Reserved. 52 属性マクロのサンプル 構文木から文字列リテラルを探して

    値を書き換えているだけ
  53. Copyright © OPTiM Corp. All Right Reserved. 53  実際にコードを展開してみるとこうなる

    属性マクロのサンプル
  54. Copyright © OPTiM Corp. All Right Reserved. 54 Copyright ©

    OPTiM Corp. All Right Reserved. さいごに
  55. Copyright © OPTiM Corp. All Right Reserved. 55  マクロを使わなくて良い方法があるのであればそうするべき

    • lazy_static!マクロはRustっぽいコードで書くがRustのコードとは互換性がなく、書き方が分からなくなる • 今ならマクロではなく定数関数で実現できるonce_cellクレートで良いのでは? マクロでないとだめですか? VS
  56. Copyright © OPTiM Corp. All Right Reserved. 56  手続きマクロの情報は少ないため、書き方を学ぶには既存クレートのソースを見るのが手っ取り早い

     マクロはやろうと思えば何でも出来てしまう言わば黒魔術。 マクロの過剰摂取はメンテ性を失うことになりかねないため、用法用量を守って正しくお使い下さい さいごに
  57. Copyright © OPTiM Corp. All Right Reserved. 57

  58. Copyright © OPTiM Corp. All Right Reserved. 58  宣言マクロ2.0として、宣言マクロのシステムが大きく変更・改善される予定

     macro_rules!で定義するマクロと違い、関数などと同じように名前解決が行われるようになる  構文も関数っぽくなる • 複数のパターンにマッチする場合はこれまでのマクロっぽい構文  解決すべき問題が多く残っており、安定化時期は未定  構文も変わるかも? 宣言マクロ2.0