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
なのに,代入しようとしている a
は char
じゃないかと.
コンパイラは同時に,代入しようとしている 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
の値を変更することができます.alphabets
は a
だけが入った配列に変更されます!