はじめに

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のコピーが代入される

参考文献

 

广告
将在 10 秒后关闭
bannerAds