はじめに
Rustで(A, B, Option<C>, Vec<D>)
のような複雑な複合型をイテレートする方法を考えます。すなわち
let x = (A {}, B {}, Some(C {}), vec![D {}, D {}]);
for y in x {
println!("{:?}", y); // A B C D D と表示したい
}
こういうことをしたい、ということですね。
簡単にできるわけではないですが、いろいろと下準備をすると実現可能です。
イテレータの返す型
当然ですがイテレータは一つの型しか返せません。今返したい型はA,B,C,D
の4つあるので、それらをまとめた型が必要です。
#[derive(Debug)]
struct A;
#[derive(Debug)]
struct B;
#[derive(Debug)]
struct C;
#[derive(Debug)]
struct D;
#[derive(Debug)]
enum Any<'a> {
A(&'a A),
B(&'a B),
C(&'a C),
D(&'a D),
}
ここでは4つをまとめる型としてAny
を作りました。
#[derive(Debug)]
は最後にprintln!
するためにつけただけで、本題とは無関係です。
Anyを返すイテレータ
次にイテレータを表す型Iter
を作ります。この型はメンバとしてイテレートすべきAny
のリストを持っています。単にlist: Vec<Any<'a>>
とせず、Anys
を定義した理由は後程説明します。
struct Anys<'a>(Vec<Any<'a>>);
struct Iter<'a> {
list: Anys<'a>,
}
このIter
に対してIteratorトレイトを実装します。
実装としては単にリストの中身をpop()
で取り出していくだけです。
impl<'a> Iterator for Iter<'a> {
type Item = Any<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.list.0.pop()
}
}
イテレータを作る
先ほど定義したイテレータを、冒頭の複合型から作る方法を考えます。まず、単に型A
の場合は簡単です。
impl<'a> From<&'a A> for Anys<'a> {
fn from(x: &'a A) -> Self {
Anys(vec![Any::A(x)])
}
}
このようなFromトレイトを実装すればA
からAnys
を作れます。さらに
impl<'a> IntoIterator for Anys<'a> {
type Item = Any<'a>;
type IntoIter = Iter<'a>;
fn into_iter(mut self) -> Self::IntoIter {
self.0.reverse();
Iter { list: self }
}
}
Anys
に対してIntoIteratorトレイトを実装すれば、Anys
を介してA
をイテレートすることができます。つまり
let x = A {};
let x: Anys = (&x).into();
for y in x {
println!("{:?}", y);
}
こういうことができるようになりました。
A
に対してもIntoIteratorトレイトを実装すれば途中のlet x: Anys = (&x).into();
も不要になりますが、ここでは省略しています。
複合型への対応
あとはFromトレイトを増やしていくだけです。B,C,D
からAnys
へのFromトレイトはA
と同じなので省略します。
Vec
とOption
、タプルを同様に定義します。
impl<'a, T: 'a> From<&'a Vec<T>> for Anys<'a>
where
&'a T: Into<Anys<'a>>,
{
fn from(x: &'a Vec<T>) -> Self {
let mut ret = Vec::new();
for x in x {
ret.append(&mut x.into().0);
}
Anys(ret)
}
}
これはVec
の場合です。型制約&'a T: Into<Anys<'a>>
を付けているので、Vec
の中身がAnys
へ変換可能な任意のVec
をこれで変換できます。
Option
とタプルも同様です。
impl<'a, T: 'a> From<&'a Option<T>> for Anys<'a>
where
&'a T: Into<Anys<'a>>,
{
fn from(x: &'a Option<T>) -> Self {
let mut ret = Vec::new();
if let Some(x) = x {
ret.append(&mut x.into().0);
}
Anys(ret)
}
}
impl<'a, T0: 'a, T1: 'a, T2: 'a, T3: 'a> From<&'a (T0, T1, T2, T3)> for Anys<'a>
where
&'a T0: Into<Anys<'a>>,
&'a T1: Into<Anys<'a>>,
&'a T2: Into<Anys<'a>>,
&'a T3: Into<Anys<'a>>,
{
fn from(x: &'a (T0, T1, T2, T3)) -> Self {
let mut ret = Vec::new();
let (t0, t1, t2, t3) = x;
ret.append(&mut t0.into().0);
ret.append(&mut t1.into().0);
ret.append(&mut t2.into().0);
ret.append(&mut t3.into().0);
Anys(ret)
}
}
これで、Vec
とOption
とA,B,C,D
をタプルで組み合わせた任意の複合型をAnys
に変換できるようになりました。
(ここでは4つのタプルしか定義していませんが、それ以外の個数も同様なので必要なだけ増やせばよいです)
また、Vec<Any<'a>>
の代わりにAnys
を定義したのはこのFromトレイトを実装するためです。Rustではあるトレイトを実装する際に「そのトレイトを定義したクレートか、対象の型を定義したクレートでしか実装できない」という制限があります。FromトレイトもVec
もこのクレートで定義したものではないので、Vec
のままだとFromトレイトは実装できません。しかし、Anys
はこのクレートで定義した型なのでFromトレイトを実装できます。
まとめ
最終的にこういうことができるようになりました。
fn main() {
let x = (A {}, B {}, Some(C {}), vec![D {}, D {}]);
let x: Anys = (&x).into();
for y in x {
println!("{:?}", y);
}
let x = (Some(D {}), C {}, vec![B {}, B {}, B {}], Some(A {}));
let x: Anys = (&x).into();
for y in x {
println!("{:?}", y);
}
}