パンの木を植えて

主として数学の話をするブログ

【Rust】ベクトルと for - in 文の使い方メモ

\[ %%% 黒板太字 %%% \newcommand{\A}{\mathbb{A}} %アフィン空間 \newcommand{\C}{\mathbb{C}} %複素数 \newcommand{\F}{\mathbb{F}} %有限体 \newcommand{\N}{\mathbb{N}} %自然数 \newcommand{\Q}{\mathbb{Q}} %有理数 \newcommand{\R}{\mathbb{R}} %実数 \newcommand{\Z}{\mathbb{Z}} %整数 %%% 2項演算 %%% \newcommand{\f}[2]{ \frac{#1}{#2} } \]

Rust で LeetCode をやっているのですが,頻繁にベクトルを for 文に突っ込む構文が出てきます.そのたびにボローチェッカに怒らて右往左往していたのですが,いい加減でまとめます.


ベクトルの要素を利用する

わかりやすいように alphabets という変数に a から z までのアルファベットを入れておきます.

let alphabets: Vec<char> = (b'a'..=b'z')
        .map(|c| c as char)            
        .filter(|c| c.is_alphabetic())
        .collect::<Vec<_>>();

これは型注釈にあるように char 要素が入った不変なベクタです.


基本として,まずベクタ alphabets の中身を順に覗いてみます.素直にベクタを iter() でイテレータに変換して,for - in 構文で回します.

for alphabet in alphabets.iter() {
    // 変数alphabetの中身を出力
    println!("{}", alphabet);
}

これの出力はこんな感じになります.

a
b
c
(中略)
z

標準で用意されている型名を調べるための関数 std::any::type_name も使って型名も調べると, for 文の中で回っている変数 alphabet の型は &char であることがわかります.参照になっているんですね.


ベクトルの要素を変更する

次に,同様にして要素の変更にチャレンジしましょう.アルファベット26文字が入っているベクタ alphabets の中身を全部 a に書き換えてみます.

エラー:中身の変数が参照になっている

こんなコードはどうでしょうか?もちろん,ベクタ alphabets は可変に宣言しなおしてあるとします.

for alphabet in alphabets.iter() {
    // 変数alphabetの中身を変更しようとしている
    alphabet = 'a';
}

宣言を mutable に変更したうえで,alphabet に 'a' を代入しているだけです.素直に考えればこんなものだと思います.


いけそうな感じがしますよね.実際 TypeScript だったら同様のコードでエラーなしに動くでしょう.しかし Rust ではこれはエラーになります.

エラーメッセージはこう.

Line 17, Char 16: mismatched types (solution.rs)
   |
15 | for alphabet in alphabets.iter() {
   |     -------- expected due to the type of this binding
16 |     // 変数alphabetの中身を変更しようとしている
17 |     alphabet = 'a';
   |                ^^^
   |                |
   |                expected `&char`, found `char`
   |                help: consider borrowing here: `&'a'`

型が違うと言われていますね.変数 alphabet&char なのに,代入しようとしている achar じゃないかと.

コンパイラは同時に,代入しようとしている a&a と借用に変更することを提案して(くださって)います.


エラー:ベクタが可変でも中身が可変でない

実際にやってみましょう.今度はこのコードを走らせてみます.

for alphabet in alphabets.iter() {
    // 変数alphabetの中身を変更しようとしている
    alphabet = &'a';
}

そうするとまたもエラーになります.エラーメッセージはこう.

Line 18, Char 5: cannot assign twice to immutable variable `alphabet` (solution.rs)
   |
15 | for alphabet in alphabets.iter() {
   |     --------
   |     |
   |     first assignment to `alphabet`
   |     help: consider making this binding mutable: `mut alphabet`
...
18 |     alphabet = &'a';
   |     ^^^^^^^^^^^^^^^ cannot assign twice to immutable variable

ベクタ alphabets が mutable で宣言されていようと,中身の alphabet は immutable だから変更できないと言われていますね.For 文の中で宣言するときに mut を付けて,こんなコードにしてみましょう.

for mut alphabet in alphabets.iter() {
    // 変数alphabetの中身を変更しようとしている
    alphabet = &'a';
}

大変に素直なコードですが,これで通ります.実際,これにより For 文の中の変数 alphabet はすべて a に変更されます.

しかし,ベクタ alphabets はそのまま.a, b, c, ... , z のままで変更されません!驚きというほどでもないですが,元のアルファベットのベクタを変更するのがやりたいことだったので,このままではまずいですね.

ベクタ alphabets の中でのインデックス i を取得しておいて,alphabets[i] = 'a' と書き直すべきでしょうか?

そうではありません.

元のベクトルが変更できないのは変数 alphabet が参照になっていて, 値そのものを持っていないからです.参照ではなく値自体を持つように変更してさえやればよいのです.


ベクタの値をFor文の中で変更する

これはベクタからイテレータを作る際に,iter() をかましているのが問題です.iter_mut() をかますように変更しましょう.

そのうえで,参照外し演算子 * をつけて alphabet が参照している値そのものにアクセスします.

for alphabet in alphabets.iter_mut() {
    // 変数alphabetの中身を変更しようとしている
    *alphabet = 'a';
}

これにより,実際にベクタ alphabets の値を変更することができます.alphabetsa だけが入った配列に変更されます!