[Rust]for文での所有権

Rust

for 〇〇 in 対象

for文を使用する際に、対象のところにベクタや配列を置いたとき、その対象の所有権がどうなるのか、仕様を確認していきます。

参考にしたページ

使用した環境
WSL2 Ubuntu - 20.04
Rust 1.62.0

 Androidアプリを作成しました。
 感情用のメモ帳です。

スポンサーリンク
スポンサーリンク

for文で使えるかどうか

本題に入る前に、for文で使える、使えない、はどうやって決まっているのかというと、そのデータ型が「Iteratorトレイト」を実装しているかどうかによります。

Iterator in core::iter - Rust

よく使いそうなデータ型を例に挙げると、

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()がよばれて変数の所有権が移動しています。

Copy in std::marker - Rust

データがヒープ領域に確保されるもののとき、変数はコピーされずに、所有権が移動します。

ベクタのときと同様に「&」をつけるか、「iterメソッド」を使うことで参照にすることができます。

タイトルとURLをコピーしました