こちらの記事は Rustマクロ冬期講習アドベントカレンダー 10日目の記事です!
前回に続き小ネタを紹介したいと思います。以下、今回の出典元です。
Metavariable Expressions - The Little Book of Rust Macros
今回は、宣言マクロの展開先に使える特殊な記法「メタ変数式」に触れたいと思います。
宣言マクロで展開先においてメタ変数またはそれと同じ個数の何かを設置する方法は
- 普通の設置
$メタ変数
- 繰り返し構文
$(...)*
の2つでした。実はそれ以外にも展開先で扱える記法・情報がありそれがメタ変数式なのですが、使い所が微妙なものばかりだったので小ネタとして扱います。工夫すれば案外使えるかも...?でもnightlyなんだよなぁ...
紹介するメタ変数式リスト(説明しやすい順に並べ替えています)
Little Bookに合わせているのですが、 $ident
がメタ変数を指しています。変数名が付けられているからその部分を識別子 ident
と表現しているのでしょう。
ドルをそのまま出力する $$
ドル $
をそのまま出力します。つまりエスケープみたいな感じですね。ドルをそのまま使う文法は macro_rules
ぐらいしかないので、つまり宣言マクロで宣言マクロを出力したい場合なんかに利用します。そんな機会ある...?
以下はLittle Bookにある例ままです。というか今回載せている例は特に断りがない限りLittle Bookのものです。
#![feature(macro_metavar_expr)]
macro_rules! foo {
() => {
macro_rules! bar {
( $$( $$any:tt )* ) => { $$( $$any )* };
}
};
}
foo!();
bar!();
foo
マクロによって bar
マクロが定義されて、実行できるようになります。
今何問目? ${index(depth)}
繰り返し構文 $(...)*
において、現在のインデックスを得るメタ変数式です。実は繰り返し構文はネストが可能で、 depth
にはどの深さのインデックスを取得するかを指定します。省略した場合、つまり ${index()}
と書いた場合は最上層、つまり ${index(0)}
だと見做されます。
実用例だと例えばこんな感じ...わざわざマクロ使う理由?ねーよ!!!!
#![feature(macro_metavar_expr)]
macro_rules! square_map {
( $( $($value:expr),* );*) => {
vec![
$(
$(
((${index()}, ${index(1)}), $value)
),*
),*
]
}
}
use std::collections::HashMap;
fn main() {
let o = true;
let x = false;
let treasure_map: HashMap<(usize, usize), bool> = square_map! {
x, x, x;
x, o, x;
x, x, x
}
.into_iter()
.collect();
for y in 0..4 {
for x in 0..4 {
match treasure_map.get(&(x, y)) { // タプルのハッシュで取れて便利でしょ?...そんなことはない...?(涙)
Some(true) => print!("o "),
Some(false) => print!("x "),
None => print!(" "),
}
}
println!();
}
dbg!(treasure_map);
}
マクロ展開後のvecの中身はこんな感じになっています。
((0, 0), x),
((1, 0), x),
((2, 0), x),
((0, 1), x),
((1, 1), o),
((2, 1), x),
((0, 2), x),
((1, 2), x),
((2, 2), x),
Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=d3d7a3ebe1951cb625f36faa256ecbfa
...筆者は良い例が思いつかなかったですが、利用方法次第では便利かもです。
メタ変数を無視! ${ignore($ident)}
展開予定のメタ変数を受け取り、何も出力しないメタ変数式です。
これ単体だと無意味ですが、繰り返し構文中で他のメタ変数式の情報だけ活用したい場合用に用いるようです。
必要性がわかるような気がする一方需要が不明すぎる
#![feature(macro_metavar_expr)]
macro_rules! repetition_tuples {
( $( ( $( $inner:ident ),* ) ; )* ) => {
($(
$(
(
${index()},
${index(1)}
${ignore($inner)} // この行を消すと何故か動かない
),
)*
)*)
};
}
fn main() {
let tuple = repetition_tuples!(
( one, two ) ;
() ;
( one ) ;
( one, two, three ) ;
);
println!("{tuple:?}");
}
((0, 0), (1, 0), (0, 2), (0, 3), (1, 3), (2, 3))
Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=5c7caac3fb61af69a1e9d71f82912630
あと2つ紹介予定なのですが正直違いがわかりにくいです!!><
繰り返し全体の長さ ${len(depth)}
インデックス取得があるなら、繰り返し全体の回数を得る方法もあるでしょう。というわけでそれが len
1 メタ変数式です!
index
の時と同じく depth
で繰り返しの深さを指定します。
#![feature(macro_metavar_expr)]
macro_rules! lets_count {
( $( $outer:ident ( $( $inner:ident ),* ) ; )* ) => {
$(
$(
println!(
"'{}' in inner iteration {}/{} with '{}' in outer iteration {}/{} ",
stringify!($inner), ${index()}, ${len()},
stringify!($outer), ${index(1)}, ${len(1)},
);
)*
)*
};
}
fn main() {
lets_count!(
many (small , things) ;
none () ;
exactly ( one ) ;
);
}
繰り返し全体をイテレータかベクトルか何かと思えばその長さなのでわかりやすいかと思います。
'small' in inner iteration 0/2 with 'many' in outer iteration 0/3
'things' in inner iteration 1/2 with 'many' in outer iteration 0/3
'one' in inner iteration 0/1 with 'exactly' in outer iteration 2/3
Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=4b582e4e00afaf2909825b30252bb20b
メタ変数の合計出現回数 ${count($ident, depth)}
長さと言うよりはメタ変数の出現回数をカウントするもののようですが、はっきりいいます。 挙動が謎です 。
#![feature(macro_metavar_expr)]
macro_rules! foo {
( $( $outer:ident ( $( $inner:ident ),* ) ; )* ) => {
println!("count(outer, 0): $outer repeats {} times", ${count($outer)});
println!("count(inner, 0): The $inner repetition repeats {} times in the outer repetition", ${count($inner, 0)});
println!("count(inner, 1): $inner repeats {} times in the inner repetitions", ${count($inner, 1)});
};
}
fn main() {
foo! {
outer () ;
outer ( inner , inner ) ;
outer () ;
outer ( inner ) ;
};
}
count(outer, 0): $outer repeats 4 times
count(inner, 0): The $inner repetition repeats 3 times in the outer repetition
count(inner, 1): $inner repeats 4 times in the inner repetitions
Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=ed5eeedc427a05fc425db42e4d304f42
1つ目と2つ目まではまだわかります。全体での出現回数になっており、確かにこれは len
メタ変数式で得られる情報とはまた異なるものです。しかし3つ目の例がよくわかりません。深さに関係なく inner
の合計マッチ回数は3回のはずですが、4になっています。 inner
が何回出てきたかではなく ( ... )
が何回出てきたかを出力しているのでしょうか?ともかく、depth = 1
の時の挙動がよくわからないですね...
まとめ・所感
使い所がイマイチなメタ変数式について解説しました、小ネタ程度に捉えていただけると幸いです!
-
Little Bookでは
length
になっていますが、どうやらその後len
に変わったようです。 ↩