for 〇〇 in 対象
for文を使用する際に、対象のところにベクタや配列を置いたとき、その対象の所有権がどうなるのか、仕様を確認していきます。
参考にしたページ
- The Rust Programming Language 日本語版
- 所有権とは? - The Rust Programming Language 日本語版
- for と range - Rust By Example 日本語版
使用した環境
WSL2 Ubuntu - 20.04
Rust 1.62.0
for文で使えるかどうか
本題に入る前に、for文で使える、使えない、はどうやって決まっているのかというと、そのデータ型が「Iteratorトレイト」を実装しているかどうかによります。
よく使いそうなデータ型を例に挙げると、
Iteratorトレイトが実装されている
- 配列
- ベクタ
- ハッシュマップ
未実装
- String
- タプル
このようになっています。
Pythonを使っている身からすると、Stringはともかく、タプルがfor文で使えないのは不便に思いましたが、タプルが配列のようなデータ型ではなく、構造体の一種(フィールド名がなく代わりに数字を使うもの)だと知って納得しました。
所有権の移動
ベクタ
ベクタをそのままfor文で使用した場合、所有権が移動します。
fn main() {
let numbers = vec![2, 4, 6, 8, 10];
for number in numbers {
println!("{}", number)
}
// この下の行がエラーになります。
println!("numbers[0]: {}", numbers[0])
}
出力結果
error[E0382]: borrow of moved value: `numbers` --> src/main.rs:7:32 | 2 | let numbers = vec![2, 4, 6, 8, 10]; | ------- move occurs because `numbers` has type `Vec<i32>`, which does not implement the `Copy` trait 3 | for number in numbers { | ------- `numbers` moved due to this implicit call to `.into_iter()` ... 7 | println!("numbers[0]: {}", numbers[0]) | ^^^^^^^ value borrowed here after move |
エラーメッセージにもあるように、numbersに対し、暗黙的に「into_iterメソッド」が呼ばれています。
この「.into_iter()」はforなどで使用できるようにイテレータに変換しますが、所有権が移動します。
所有権を移動させないようにするには、ベクタに対し、「iterメソッド」か、「&」を付けて参照するようにします。
fn main() {
let numbers = vec![2, 4, 6, 8, 10];
for number in numbers.iter() {
println!("{}", number)
}
println!("numbers[0]: {}", numbers[0])
}
2 4 6 8 10 numbers[0]: 2
Iteratorトレイトを実装済みのハッシュマップにおいても同様です。
use std::collections::HashMap;
fn main() {
let mut points = HashMap::new();
points.insert("Red", 100);
points.insert("White", 120);
for (color, point) in &points {
println!("{}: {}", color, point);
}
println!("{:?}", points);
}
White: 120 Red: 100 {"White": 120, "Red": 100}
もし&をつけていなかった場合、上のコードはエラーになります。
iter_mutメソッドで参照してかつ変更可能にします。
fn main() {
let mut names = vec![
"aoki".to_string(),
"fujino".to_string(),
"yamada".to_string(),
];
for name in names.iter_mut() {
*name = name.to_uppercase();
}
println!("names: {:?}", names);
// names: ["AOKI", "FUJINO", "YAMADA"]
}
「for name in &mut names」としても同じ効果が得られます。
どちらにせよ、その後で参照外し「*」が必要です。
配列
配列は、その中身のデータ型によって、所有権が移動するかどうかが異なります。
整数や浮動小数、文字など、スタック領域に確保されるものであれば、所有権は移動しません。
fn main() {
let numbers = [3, 56, 2, 12, 1];
for number in numbers {
println!("{}", number)
}
println!("numbers[0]: {}", numbers[0])
}
3 56 2 12 1 numbers[0]: 3
forの後で、numbersの0番目にアクセスすることができています。
今度は配列の中身をString型に変えてやってみましょう。
fn main() {
let numbers = ["62".to_string(), "53".to_string(), "11".to_string()];
for number in numbers {
println!("{}", number)
}
// この下の行がエラーになります。
println!("numbers[0]: {}", numbers[0])
}
error[E0382]: borrow of moved value: `numbers` --> src/main.rs:7:32 | 2 | let numbers = ["62".to_string(), "53".to_string(), "11".to_string()]; | ------- move occurs because `numbers` has type `[String; 3]`, which does not implement the `Copy` trait 3 | for number in numbers { | ------- `numbers` moved due to this implicit call to `.into_iter()` ... 7 | println!("numbers[0]: {}", numbers[0]) | ^^^^^^^^^^ value borrowed here after move |
[String; 3]はコピートレイトが実装されておらず、into_iter()がよばれて変数の所有権が移動しています。
データがヒープ領域に確保されるもののとき、変数はコピーされずに、所有権が移動します。
ベクタのときと同様に「&」をつけるか、「iterメソッド」を使うことで参照にすることができます。