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を開催した。
二重かっこの謎が気になる...。
今週のプルリクエストはこちら。