1ヶ月近く空いてしまったが第55回です。前回はこちら。
[第55回の様子]
2022/10/26に第55回を開催した。
内容としてはRust By Example 日本語版の17. macro_rules!の「17.2. DRY (Don't Repeat Yourself)」〜「17.4. 可変個引数」に取り組んだ。
参加者は自分を入れて6人。開催頻が低い割に参加者が安定していて助かる。今回も自分がドライバーを担当した。
[学んだこと]
- 17.2. DRY (Don't Repeat Yourself)
- マクロは共通部分を抽出して繰り返しを避けるのに使える
- 例として、Vec<T>型に対して
+=
、-=
演算子を実装する
// 演算子を定義するマクロ macro_rules! op { ($func:ident, $bound:ident, $method:ident) => { fn $func<T: $bound<T, Output=T> + Copy>( xs: &mut Vec<T>, ys: &Vec<T> ) { // zipは2つのイテレータを同時に回す for (x, y) in xs.iter_mut().zip(ys.iter()) { *x = $bound::$method(*x, *y); // *x = x.$method(*y); } } }; }
- 上記のマクロを利用するとfuncで指定した名前の関数が生成される
op!(add_assign, Add, add); op!(sub_assign, Sub, sub); fn main() { let mut vec1 = Vec::new(); vec1.push(1); vec1.push(2); let mut vec2 = Vec::new(); vec2.push(2); vec2.push(3); add_assign(&mut vec1, &vec2); println!("{:?}", vec1); // [3, 5] println!("{:?}", vec2); // [2, 3] }
- マクロを使ってテストを簡略化することもできる
mod test { use std::iter; macro_rules! test { ($func:ident, $x:expr, $y:expr, $z:expr) => { #[test] fn $func() { // 配列の長さ0〜9まででテスト for size in 0usize..10 { let mut x: Vec<_> = iter::repeat($x).take(size).collect(); let y: Vec<_> = iter::repeat($y).take(size).collect(); let z: Vec<_> = iter::repeat($z).take(size).collect(); super::$func(&mut x, &y); assert_eq!(x, z); } } }; } // add_assign、sub_assignをテスト test!(add_assign, 1u32, 2u32, 3u32); test!(sub_assign, 3u32, 2u32, 1u32); }
- 17.3. Domain Specific Languages (ドメイン特化言語、DSLs)
- マクロを利用すると特定の機能のための簡潔・直感的な構文を定義できる
- 以下は簡単な計算APIの例
// 計算式と計算結果を出力するマクロ macro_rules! calculate { // evalはRustのキーワードではない:ここではマクロテンプレートの一部 (eval $e:expr) => {{ { let val: usize = $e; // 型を整数に制約 println!("{} = {}", stringify!{$e}, val); } }}; } fn main() { calculate! { eval 1 + 2 } // 1 + 2 = 3 calculate! { eval (1 + 2) * (3 / 4) } // (1 + 2) * (3 / 4) = 0 }
- なんか
{{ { ... } }}
と三重になってるのは翻訳サイトのミスっぽい。ただ、元のサイト見ても二重になってて何か意味があるっぽかった。ただ、1個だけにしても動いたので謎。 - 17.4. 可変個引数
- 可変個引数のインターフェースを利用して、先ほどの計算APIを拡張するとこうなる
macro_rules! calculate { // 単一の`eval`のためのパターン (eval $e:expr) => {{ { let val: usize = $e; println!("{} = {}", stringify!{$e}, val); } }}; // 複数の`eval`を再帰的に分解 (eval $e:expr, $(eval $es:expr),+) => {{ calculate! { eval $e } calculate! { $(eval $es),+ } }}; } fn main() { calculate! { eval 1 + 2, eval 3 + 4, eval (2 * 3) + 1 } }
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
二重かっこの謎が気になる...。
今週のプルリクエストはこちら。