###### tags: `Rust` `macro` # Rust マクロ 書き方 https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/macros.html https://keens.github.io/blog/2018/02/17/makurokurabu_rustshibu/ 例: vec!マクロの実装 ## macro_rules! マクロ マクロの宣言はいつもこのワードから ```rust= macro_rules! vec { } ``` ## マッチング マクロは引数をパターンマッチして処理を決定する。 ```rust= macro_rules! vec { (パターン) => {式}; (パターン) => {式}; (パターン) => {式}; (パターン) => {式}; } ``` match式に似ているが、コンパイル時にマッチを行うことになる。 => の左にあるパターンは**マッチャ(mathcer)**と呼ばれる mathcerは小さな独自の構文を持つ ## matcher構文 - $x:expr 任意の式にマッチする。 - $x = **メタ引数** - expr = フラグメント指定子 マッチャ中に登場するパターンは、正確に一致する必要がある。 ```rust= macro_rules! foo { (x => $e:expr) => (println!("mode X: {}", $e)); (y => $e:expr) => (println!("mode Y: {}", $e)); } fn main() { foo!(y => 3); } ``` foo! マクロの引数を (z => 4) とかにするとエラー なぜなら、 (z => $e:expr) は定義されてないから ## フラグメント指定子 いわゆる、メタ変数の型 - ident : 識別子。 実際のコードの変数名などを表す(x, foo) - path : パッケージ内の関数などへのパス - expr : 式 - ty : 型 - pat : パターン - stmt : 文 - block: {} で区切られた文 - item : - meta : メタアイテム [cfg(target_os = "window")] などのアトリビュート - tt : トークンツリー 特に使いそうなのは、 **expr, ty, tsmt, block** か 実際に繰り返しのコードを書いてしまったときなど、その構文を解析して メタ変数に分解する能力が求められそう。 ## マクロ区切りのカッコ マクロの=>の右辺の、一番外側のカッコは識別のためのカッコなので ()でも{}でも[]でもいい。 呼び出し側のカッコも (), [], {} どれでもいい ```rust= macro_rules! get { ( $( $e: expr )* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($e); )* temp_vec } }; } fn main() { let mut vec1 = get!(1 2 3); let mut vec2 = get![4 5 6]; let mut vec3 = get!{7 8 9}; println!("{:?}, {:?}, {:?}", vec1, vec2, vec3) } ``` ちなみに、マクロ内のコードは展開されるので、変数を使うとき マクロ展開前コードにある名前と一致してしまうとエラーを招く。 マクロの中で一時的な変数(swap!()のtempなど)を使うなら マクロのカッコの中に、更に{ } でスコープを区切ると良い ```rust= macro_rules! vec { (パターン) => { { } }; } ``` ## 繰り返しマクロ 複数のパターンを繰り返しと判断して、マクロ展開式の中で繰り返し部分を作ることができる。 ### マッチ左辺 マクロのパターンを $ pat * で区切れる patの後ろに、区切り文字をおける - , - ; - (なし) = スペース ### マッチ右辺 $( 処理 )* で繰り返すコードを書く ()は必須 ### マクロ呼び出し元 macro! のあとに () か [] を書き、その中に区切り文字とともに 要素を書いていく 例. 一つのメタ変数(式)に対して, カンマ区切りで繰り返し ```rust= macro_rules! multi_print { ( $( $e: expr ),* ) => { { $( println!("{}", $e); )* } }; } fn main() { multi_print!(1, 2, 3, 4, 5, 6) } ``` パターンの中にも()や, ; を書けて、タプル引数みたいな感じにもできる 例. 2つのメタ変数(式, 型) を(e, t), タプルとカンマ括りで繰り返し ```rust= macro_rules! multi_print { ( $( ($e: expr, $t: ty) ),* ) => { { $( println!("{}", $e); )* } }; } fn main() { multi_print!((1, i32), (2,i32)) } ``` # 最後に : マクロは最終手段である マクロ機能の メリット - 構文レベルの抽象化 - 冗長な記述を簡略表記できる - 繰り返しできる これらは、ソースコードの短縮、簡略化につながる。 (コードは展開されるので、プログラムのサイズは変わらない) デメリット - 組み込みルールが少なく、理解しづらい メリットは多分にあるものの、コードの理解しづらさが上がる。 **マクロを書くな、パターンをカプセル化する唯一の方法のときだけマクロを書け**というルールすら存在する ただし、マクロを書くのと同等の関数に比べて、呼び出しが楽になるならマクロを書いても構わないとも行っている。 マクロを書く場合は、それをマクロと意識せず関数と同等に使えるような 使い勝手の良さで実装するべきである。