電気ひつじ牧場

技術メモ

Rustにっき/12日目・イテレータ

久々のにっきです。 イテレータを使うとよりスマートな書き方ができるようなので勉強していきます。

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"]