はじめに
Rustにはイテレータというコレクション型に対する便利な仕組みがあります。
ですが実際に使ってみると型が値か参照かで結構戸惑います。
またイテレータにも何種類かあり、ややこしいので調べたことをまとめました。
イテレータとは
イテレータとは配列やベクタ等の複数の要素を持つデータ型に対して
先頭から順番に各要素にアクセスできる仕組みのことです。
イテレータを使うことで添え字を使わずに要素にアクセスしたり、複雑な処理を簡潔に書くことができます。
iter
イテレータはiter
で取得できます。
iter
が返すのは各要素への参照です。
let nums: Vec<i32> = vec![0, 1, 2, 3, 4];
for x in nums.iter() { //変数xの型は&i32
println!("{}", x);
}
以下ではよく使う関数をいくつか紹介します。
for_each
各要素に対して処理を適用させたい場合はfor_each
も使えます。
引数にクロージャ(無名関数)を渡します。
上記のコードはfor_each
で置き換えると下記の様になります。
nums.iter().for_each(|x: &i32| println!("{}", x));
sum
sum
でイテレータ内の数値の合計を求められます。
let nums: Vec<i32> = vec![0, 1, 2, 3, 4];
// 10
let sum: i32 = nums.iter().sum();
fold
fold
でイテレータ内の要素を元に1つの返り値を得ることができます。
第1引数が初期値、第2引数が各要素に適用させるクロージャです。
クロージャの第1引数は各要素に適用される毎に更新されます。
下記のコードではイテレータ内の数値の総乗を求めています。
let nums = vec![1, 2, 3, 4, 5];
// 120
let total: i32 = nums.iter().fold(1, |total: i32, x: &i32| total * x);
map
map
を使うとイテレータの各要素に対して処理を適用させて、新しいイテレータを生成することができます。
下記のコードでは数値文字列のイテレータから数値のイテレータを生成して総和を求めています。
let str_nums: Vec<&str> = vec!["1", "3", "5", "7", "9"];
// 25
let sum: i32 = str_nums.iter().map(|x: &&str| x.parse::<i32>().unwrap()).sum();
またcollect
でイテレータをベクタに変換できます。
let nums: Vec<i32> = vec![0, 1, 2, 3, 4];
// [10, 11, 12, 13, 14]
let nums2: Vec<i32> = nums.iter().map(|x: &i32| x + 10).collect();
filter
filter
で条件に一致する要素を抽出できますが、いくつか注意点があります。
クロージャに渡される要素の型は参照への参照なので*
を付けないと値を取得することできません。
また、filter
が返すのは一致した要素への参照のイテレータです。
let nums: Vec<i32> = vec![0, 1, 2, 3, 4];
let nums2: Vec<&i32> = nums.iter().filter(|x: &&i32| *x % 2 == 0).collect();
抽出した結果を値のベクタとしたい場合は、cloned
でコピーを作成する必要があります。
let nums: Vec<i32> = vec![0, 1, 2, 3, 4];
let nums2: Vec<i32> = nums.iter().cloned().filter(|x: &&i32| *x % 2 == 0).collect();
参照の分配
クロージャの引数に&
を付けると参照外しが行われた状態で引数に渡されます。
これを参照の分配といいます。
let nums: Vec<i32> = vec![0, 1, 2, 3, 4];
// クロージャの引数xの型は&&i32
let sum: i32 = nums.iter().filter(|x| *x % 2 == 0).sum();
// クロージャの引数xの型は&i32
let sum: i32 = nums.iter().filter(|&x| x % 2 == 0).sum();
// クロージャの引数xの型はi32
let sum: i32 = nums.iter().filter(|&&x| x % 2 == 0).sum();
参照の分配を行うと型注釈は常に&_
になります。
なので下記のコードはコンパイルエラーになります。
let sum: i32 = nums.iter().filter(|&x: &i32| x % 2 == 0).sum();
また、参照の分配で引数の型を値にするとコピーが渡されます。
そのためCopyトレイトが実装されていない型だとコンパイルエラーになります。
let strings: Vec<String> = vec!["A".to_string(), "BC".to_string(), "DEF".to_string()];
// String型はCopyトレイトが実装されていないのでコンパイルエラー
let filtered: Vec<&String> = strings.iter().filter(|&&s| s.len() == 2).collect();
この場合、引数の型を参照にするとコピーが発生しないのでコンパイルが通ります。
let filtered: Vec<&String> = strings.iter().filter(|&s| s.len() == 2).collect();
iter_mut
イテレータで各要素の値を変更をしたい場合はiter_mut
を呼び出します。
iter_mut
は各要素への可変参照を返します。
let mut nums: Vec<i32> = vec![0, 1, 2, 3, 4];
for x in nums.iter_mut() { // 変数xの型は&mut i32
*x *= 2;
}
println!("{:?}", nums); // [0, 2, 4, 6, 8]
上記のコードをfor_each
で置き換えると下記の様になります。
let mut nums: Vec<i32> = vec![0, 1, 2, 3, 4];
nums.iter_mut().for_each(|x: &mut i32| *x *= 2);
println!("{:?}", nums); // [0, 2, 4, 6, 8]
可変参照の分配
可変参照においても参照の分配は可能ですが、意図した動作を行いません。
下記のコードではnums内の要素は変化していません。
これはクロージャの引数に要素のコピーが渡されるためです。
let mut nums: Vec<i32> = vec![0, 1, 2, 3, 4];
nums.iter_mut().for_each(|&mut mut x| x *= 2);
println!("{:?}", nums); // [0, 1, 2, 3, 4]
into_iter
iter
とiter_mut
は所有権の移動は発生しませんが、into_iter
は所有権が移動します。
into_iter
の返り値は参照ではなく値になります。
let nums = vec![0, 1, 2, 3, 4];
for x in nums.into_iter() { // 変数xの型はi32
println!("{}", x);
}
println!("{:?}", nums); // 所有権が移動したのでnumsは使えない
クロージャの引数の型は値か参照になります。
let nums = vec![0, 1, 2, 3, 4];
let nums2: Vec<i32> = nums
.into_iter()
.map(|x: i32| x + 10) // 引数の型は値
.filter(|x: &i32| x % 2 == 1) // 引数の型は参照
.collect();
println!("{:?}", nums2); // [11, 13]
クロージャの引数にmut
を付けると各要素を可変な変数として扱うことができます。
let strings: Vec<String> = vec!["A".to_string(), "B".to_string(), "C".to_string()];
strings.into_iter().for_each(|mut s| {
s += "_Suffix";
println!("{}", s);
});
まとめ
イテレータ
関数名 | 可変か不変か | イテレータの返り値 | 所有権 |
---|---|---|---|
iter |
不変 |
&T (参照) |
移動しない |
iter_mut |
可変 |
&mut T (可変参照) |
移動しない |
into_iter |
不変(mut で可変) |
T (値) |
移動する |
参照の分配
変数の先頭に&
を付けると、参照外しをしてから変数に代入される。
参照外しで型が値になると参照先変数のコピーが代入される。
let x: i32 = 5;
let y = &x; // 変数yの型は&i32 変数xへの参照
let &z = &x; // 参照の分配により変数zの型はi32 変数xのコピーが代入される
参考文献