一週空いて第54回です。前回はこちら。
[第54回の様子]
2022/09/28に第54回を開催した。
内容としてはRust By Example 日本語版の17. macro_rules!の「17.1. 構文」に取り組んだ。
参加者は自分を入れて3人。最近は開催頻度も低いので参加者も少なめ。久しぶりにドライバーを担当した。
教材的にはあと半年くらいは続けられそうだけど、そろそろ違う題材にしようかなあ...。
[学んだこと]
- 17.1.1. 識別子
- マクロの引数は先頭に$がつく
- マクロの引数の型には識別子designatorsが利用できる
- 以下はidentを使う例
// 関数を作成するマクロ macro_rules! create_function { ($func_name:ident) => { fn $func_name() { // stringifyマクロはidentを文字列に変換 println!("You called {:?}()", stringify!($func_name)); } }; } create_function!(foo); // fooという関数を作る create_function!(bar); // barという関数を作る fn main() { foo(); // You called "foo"() bar(); // You called "bar"() }
- 以下はexprを使う例
// 式の結果を出力するマクロ macro_rules! print_result { ($expression:expr) => { println!("{:?} = {:?}", stringify!($expression), $expression); }; } fn main() { print_result!(1u32 + 1); // "1u32 + 1" = 2 // ブロックも式の一種 print_result!({ let x = 1u32; x * x + 2 * x - 1 }); // "{ let x = 1u32; x * x + 2 * x - 1 }" = 2 }
- 17.1.2. オーバーロード
- マクロルールはmacro_rules!という複数形の名前で定義することからも分かる通り、異なる引数をとってオーバーロードが可能
- matchによるパターンマッチのような感じ
macro_rules! test { // テンプレートは自由。この場合は「test!(式1; and 式2);」のように呼び出す ($left:expr; and $right:expr) => { println!("{:?} and {:?} is {:?}", stringify!($left), stringify!($right), $left && $right) }; // 末尾にセミコロンが必要 ($left:expr; or $right:expr) => { println!("{:?} or {:?} is {:?}", stringify!($left), stringify!($right), $left || $right) }; } fn main() { test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32); // "1i32 + 1 == 2i32" and "2i32 * 2 == 4i32" is true test!(true; or false); // "true" or "false" is true }
- 折角なので、xorのパターンを実装してみる
macro_rules! test { // andとorは省略 ($left:expr; xor $right:expr) => { println!("{:?} xor {:?} is {:?}", stringify!($left), stringify!($right), $left ^ $right) }; } fn main() { test!(true; xor false); // "true" xor "false" is true test!(true; xor true); // "true" xor "true" is false }
- 存在しないパターンを呼び出したらコンパイルエラーとなった(それはそう
macro_rules! test { // and, or, xorが定義されている。省略。 } fn main() { // 定義されていないnandを呼び出す test!(true; nand false); } // 以下のコンパイルエラー error: no rules expected the token `nand` --> src/main.rs:29:17 | 5 | macro_rules! test { | ----------------- when calling this macro ... 29 | test!(true; nand false); | ^^^^ no rules expected this token in macro call
- 17.1.3. 繰り返し
- マクロの引数は繰り返しを定義できる:可変長引数みたいな感じっぽい
- マッチ対象の引数を
$(...),+
で囲むと1回以上の繰り返し - マッチ対象の引数を
$(...),*
で囲むと0回以上の繰り返し
- マッチ対象の引数を
- サンプルのマクロはこんな感じ
macro_rules! find_min { // 基本となるケース ($x:expr) => ($x); // `$x`に少なくとも1つの`$y`が続く場合 ($x:expr, $($y:expr),+) => ( // 再帰 std::cmp::min($x, find_min!($($y),+)) ) } fn main() { println!("{}", find_min!(1)); // 1 println!("{}", find_min!(1 + 2, 2)); // 2 println!("{}", find_min!(5, 2 * 3, 4)); // 4 }
- ちょっと難しいけど、
std::cmp::min($x, find_min!($($y),+))
のfind_min!($($y),+)
の部分は、引数の個数に応じて基本ケースか$(...),+
のケースかが呼び出されるんだろうな、と理解した。 $(...),*
を使った実装もやってみようと思ったのだけど、時間が足りず断念。なんかいい例があるかなあ...
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
これでマクロの基本構文はバッチリ、のはず...。
今週のプルリクエストはこちら。