Rustのタプルに対する.map()をマクロで実現する
マクロ定義
以下のtuple_map!()で実現できる。なるべくシンプルになるように心がけた。
code:rs
macro_rules! tuple_map {
( ($( $x:expr ),*), $name:ident, $y:expr ) => {
($(
{let $name = $x; $y},
)*)
};
}
追記: crateになった
再利用できるようにcrateにした。
以下でインストールできる。
code:Cargo.toml
以下で使えるようになる。
code:rs
use tuple_map::tuple_map;
使い方
シンプルな例
10と"hello"と1.23という異なる型に対して同じformat!()という処理を適用して新しいtupleを返せる。
code:rs
fn main() {
let a = tuple_map!((10, "hello", 1.23), x, {
format!("{:?}", x)
});
println!("{:?}", a);
// => ("10", "\"hello\"", "1.23")
}
aは(String, String, String)型になる。
以下が出力結果。
code:出力
("10", "\"hello\"", "1.23")
他の例
上記はmapしたあとは全部Stringになっていたが、タプルなのですべて異なる型にすることもできる。
.mapというかforしたい感じの処理をタプルとして渡してそれを実行する例。
タプルには関数たちを入れる。
関数の引数は同じで中でVecに破壊的操作もしていて、戻り値が異なる。
戻り値が異なるためタプルにする必要がある。
code:rs
fn f1(vec: &mut Vec<i32>) -> u32 {
vec.push(1);
10
}
fn f2(vec: &mut Vec<i32>) -> &'static str {
vec.push(2);
"hello"
}
fn f3(vec: &mut Vec<i32>) -> f32 {
vec.push(3);
1.2
}
fn main() {
let mut vec: Vec<i32> = Vec::new();
let a = tuple_map!((f1, f2, f3), f, {
f(&mut vec)
});
println!("a = {:?}", a);
// => a = (10, "hello", 1.2)
println!("vec = {:?}", vec);
}
以下が出力。
code:出力
a = (10, "hello", 1.2)
実際に使うときはf1(), f2(), f3()にはもう少しロジックがあることを想定している。そのロジックの成果物を戻り値として返すが関数ごとにその型が異なるという需要を想定してる。関数たちは静的に決まるため戻り値を列挙したenumを定義することでIteratorのmapなども使えそうだがenumは識別するtagの分余計にメモリ使うと思うのでタプルを使いたかった。またタプルだと型の順番と個数が決まるため、enumと比べてより安全にパターンマッチで取り出すこともできると思う。
そして処理の内部で副作用を起こすことも可能なことを示している。
参考