LoginSignup
11
5

More than 1 year has passed since last update.

はじめに

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

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

参考文献

11
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5