久々のにっきです。 イテレータを使うとよりスマートな書き方ができるようなので勉強していきます。
iter, into_iter
イテレータ
next()
の呼び出しごとに値を生成するものです。
Iterator
を実装した型はイテレータになります。イテレータが生成する値をアイテムといい、アイテムを受け取るコードを消費者と言います。
イテラブル
IntoIterator
を実装した型です。この型の値に対してinto_iter()
を呼び出すとイテレータを取得できます。全てのイテレータに対してこれを呼び出すと自分自身を返すので、イテレータはイテラブルです。
例
ベクタはイテラブルなので、以下の2つのコードは等価です。
let v = vec![1, 2, 3, 4]; for e in &v { ... }
let mut it = (&v).into_iter(); while let Some(e) = it.next() { ... }
参照とIntoIterator
共有参照に対するイテレータの生成
アイテムへの共有参照を生成するイテレータを返します。値に対してiter()
を呼び出すのと等価です。
let v = vec![1,2,3]; let mut it = (&v).into_iter(); //共有参照に対してinto_iter()の呼び出し while let Some(e) = it.next() { println!("{}", e); } println!("{:?}", v);
結果
1 2 3 [1, 2, 3]
可変参照に対するイテレータの生成
アイテムへの可変参照を生成するイテレータを返します。値に対してiter_mut()
を呼び出すのと等価です。
値に対するイテレータの生成
コレクションの所有権がイテレータに移動し、生成するアイテムの所有権は消費者に移ります。
let v = vec!["hoge".to_string(), "bar".to_string()]; let mut it = v.into_iter(); while let Some(e) = it.next() { println!("{}", e); } println!("{:?}", v);
結果
error[E0382]: borrow of moved value: `v` --> src/main.rs:9:22
移動が発生するため、v
を再び使用すると怒られてしまいます。
イテレータがスコープから外れてドロップされると、v
そのものとv
の要素も連鎖的にドロップされます。
drain
コレクションへの可変参照を受け取り、値の所有権を消費者に移動させるようなイテレータを返します。
すぐ上で出てきた値に対するinto_iter()
はコレクションそのものの所有権がイテレータに移動してしまうのに対して、これは借用しているだけです。
let mut v = vec!["hoge".to_string(), "bar".to_string()]; { let mut it = (&mut v).drain(1..); //drainする範囲指定 let _ = it.next(); } println!("{:?}", v);
結果
["hoge"]
v[1]
である"bar".to_string()
だけがdrain()
で移動されるので、v
には0番目の要素だけが残ります。
イテレータアダプタ
イテレータに対して呼び出します。イテレータを消費し、何かを行ってから別のイテレータを作る関数のことです。
map
イテレータの個々の値をクロージャに値渡しして、クロージャの戻り値から新たなイテレータを生成します。
この新たなイテレータが消費される時、クロージャから返ってきた値の所有権は消費者に移動されます。
例
let s = "a = b".to_string(); let v:Vec<_> = s.split("=").map(str::trim).collect(); //collect()でベクタに集める println!("{:?}", v);
出力
["a", "b"]
filter
イテレータの個々の値をクロージャに共有参照渡しして、クロージャで定義された条件が満たされる要素から新たなイテレータを生成します。
例
let v: Vec<&str> = vec!["aa", "bb", "cc"]; let f: Vec<_> = v.into_iter().filter(|s| *s != "aa").collect(); println!("{:?}", f); // println!("{:?}", v);
出力
["bb", "cc"]
filter(|s| *s != "aa")
と参照外しをしているのはクロージャに渡されるのが共有参照だからです。(s
の型は&&str
です。)
v.into_iter()
で値からイテレータを生成するため、コメントアウトを外すとエラーになってしまいます。(v
が所有するベクタはf
に移動されるから)
以下のようにすると、f
への移動は発生しません。
let v: Vec<&str> = vec!["aa", "bb", "cc"]; let f: Vec<_> = v.iter().filter(|s| **s != "aa").collect(); println!("{:?}", f); println!("{:?}", v);
出力
["bb", "cc"] ["aa", "bb", "cc"]
v.iter()
ではアイテムへの共有参照を生成するイテレータが返されます((&v).into_iter()
でも同様)。したがってfilter()
のクロージャに渡されるs
の型は&&&str
です。ややこしいですが、&str
のベクタから&&str
のアイテムをもつイテレータを作成し、それがfilter()
のクロージャに&&&str
として渡されます。
連結
map()
, filter()
は別のイテレータを生成するので、それに対する結果をマップ、フィルターすることもできます
let s = "a, b, c, d".to_string(); let v:Vec<_> = s.split(",").map(str::trim).filter(|s| *s != "b").collect(); println!("{:?}", v);
出力
["a", "c", "d"]