ベクタで一連の値を保持する
最初に見るコレクションは、Vec<T>
であり、ベクタとしても知られています。ベクタは、
メモリ上に値を隣り合わせに並べる単独のデータ構造に2つ以上の値を保持させてくれます。
ベクタには、同じ型の値しか保持できません。要素のリストがある場合に有用です。
例えば、テキストファイルの各行とか、ショッピングカートのアイテムの価格などです。
新しいベクタを生成する
新しい空のベクタを作るには、リスト8-1に示されたように、Vec::new
関数を呼ぶことができます。
# #![allow(unused_variables)] #fn main() { let v: Vec<i32> = Vec::new(); #}
リスト8-1: 新しい空のベクタを生成してi32
型の値を保持する
ここでは、型注釈を付け足したことに注目してください。このベクタに対して、何も値を挿入していないので、
コンパイラには、どんなデータを保持させるつもりなのかわからないのです。これは重要な点です。ベクタは、
ジェネリクスを使用して実装されているのです; 独自の型でジェネリクスを使用する方法については、
第10章で解説します。今は、標準ライブラリにより提供されているVec<T>
型は、どんな型でも保持でき、
特定のベクタが特定の型を保持するとき、その型は山かっこ内に指定されることを知っておいてください。
リスト8-1では、コンパイラにv
のVec<T>
は、i32
型の要素を保持すると指示しました。
より現実的なコードでは、一旦値を挿入したら、コンパイラは保持させたい値の型をしばしば推論できるので、
この型注釈をすることは滅多にありません。初期値のあるVec<T>
を生成する方が一般的ですし、
Rustには、利便性のためにvec!
というマクロも用意されています。このマクロは、
与えた値を保持する新しいベクタ型を生成します。リスト8-2では、1
、2
、3
という値を持つ新しいVec<i32>
を生成しています。
# #![allow(unused_variables)] #fn main() { let v = vec![1, 2, 3]; #}
リスト8-2: 値を含む新しいベクタを生成する
初期値のi32
値を与えたので、コンパイラは、v
の型がVec<i32>
であると推論でき、型注釈は必要なくなりました。
次は、ベクタを変更する方法を見ましょう。
ベクタを更新する
ベクタを生成し、それから要素を追加するには、リスト8-3に示したように、push
メソッドを使用できます。
# #![allow(unused_variables)] #fn main() { let mut v = Vec::new(); v.push(5); v.push(6); v.push(7); v.push(8); #}
リスト8-3: push
メソッドを使用してベクタ型に値を追加する
あらゆる変数同様、第3章で議論したように、値を変化させたかったら、mut
キーワードで可変にする必要があります。
中に配置する数値は全てi32
型であり、コンパイラはこのことをデータから推論するので、
Vec<i32>
という注釈は必要なくなります。
ベクタをドロップすれば、要素もドロップする
他のあらゆる構造体
同様、ベクタもスコープを抜ければ、解放されます。リスト8-4に注釈したようにですね。
# #![allow(unused_variables)] #fn main() { { let v = vec![1, 2, 3, 4]; // vで作業をする } // <- vはここでスコープを抜け、解放される #}
リスト8-4: ベクタとその要素がドロップされる箇所を示す
ベクタがドロップされると、その中身もドロップされます。つまり、保持されていた整数値が、 片付けられるということです。これは一見単純な点に見えるかもしれませんが、ベクタの要素への参照を導入した途端、 もうちょっと複雑になる可能性を秘めています。次は、それに挑んでいきましょう!
ベクタの要素を読む
もうベクタを生成し、更新し、破壊する方法を知ったので、コンテンツを読む方法を知るのはいいステップアップです。 ベクタに保持された値を参照する方法は2つあります。例では、さらなる明瞭性を求めて、 これらの関数から返る値の型を注釈しました。
リスト8-5に示したのは、両メソッドがベクタの値に対して、添字記法とget
メソッドによりアクセスするところです。
# #![allow(unused_variables)] #fn main() { let v = vec![1, 2, 3, 4, 5]; let third: &i32 = &v[2]; let third: Option<&i32> = v.get(2); #}
リスト8-5: 添字記法かget
メソッドを使用してベクタの要素にアクセスする
ここでは、2つのことに気付いてください。まず、3番目の要素を得るのに2
という添え字の値を使用していることです:
ベクタは、数値により順序付けされ、添え字は0から始まります。2番目に、3番目の要素を得る2つの方法は、
&
と[]
を使用して参照を得るものと、番号を引数としてget
メソッドに渡して、Option<&T>
を得るものということです。
Rustには要素を参照する方法が2通りあるので、ベクタに要素が含まれない番号の値を使用しようとした時に、 プログラムの振る舞いを選択できます。例として、ベクタに5つ要素があり、番号100の要素にアクセスを試みた場合、 プログラムがすることを確認しましょう。リスト8-6に示したようにですね。
# #![allow(unused_variables)] #fn main() { let v = vec![1, 2, 3, 4, 5]; let does_not_exist = &v[100]; let does_not_exist = v.get(100); #}
リスト8-6: 5つの要素を含むベクタの100番目の要素にアクセスしようとする
このコードを走らせると、最初の[]
メソッドはプログラムをパニックさせます。存在しない要素を参照しているからです。
このメソッドは、ベクタの終端を超えて要素にアクセスしようした時にプログラムをクラッシュさせたい場合に最適です。
get
メソッドがベクタ外の番号を渡されると、パニックすることなくNone
を返します。
普通の状態でも、ベクタの範囲外にアクセスする可能性がある場合に、このメソッドを使用することになるでしょう。
そうしたら、コードにはSome(&element)
かNone
を扱うロジックが存在することになります。そう、
第6章で議論したように。例えば、番号は人間に数値を入力してもらうことで得ることもできます。
もし大きすぎる値を誤って入力し、プログラムがNone
値を得てしまったら、現在ベクタに幾つ要素があるかをユーザに教え、
再度正しい値を入力してもらうことができるでしょう。その方が、タイプミスでプログラムをクラッシュさせるより、
ユーザに優しくなるでしょう。
プログラムに有効な参照がある場合、borrow checker(借用精査機)は(第4章で解説しましたが)、 所有権と借用規則を強制し、ベクタ型の中身へのこの参照や他のいかなる参照も有効であり続けることを保証してくれます。 同一スコープ上では、可変と不変な参照を同時には存在させられないというルールを思い出してください。 このルールはリスト8-7にも適用され、リスト8-7ではベクタの最初の要素への不変参照を保持し、 終端に要素を追加しようとしていますが、動きません。
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
リスト8-7: 要素への参照を保持しつつ、ベクタに要素を追加しようとする
このコードをコンパイルすると、こんなエラーになります:
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
(エラー: 不変としても借用されているので、`v`を可変で借用できません)
|
4 | let first = &v[0];
| - immutable borrow occurs here
| (不変借用はここで発生しています)
5 |
6 | v.push(6);
| ^ mutable borrow occurs here
| (可変借用は、ここで発生しています)
7 | }
| - immutable borrow ends here
| (不変借用はここで終了しています)
リスト8-7のコードは、一見動くはずのように見えるかもしれません: なぜ、最初の要素への参照が、 ベクタの終端への変更を気にかける必要があるのでしょうか?このエラーは、ベクタの動作法のせいです: 新規要素をベクタの終端に追加すると、ベクタが現在存在する位置に隣り合って要素を入れるだけの領域がなかった場合に、 メモリの新規確保をして古い要素を新しいスペースにコピーする必要があるかもしれないからです。 その場合、最初の要素を指す参照は、解放されたメモリを指すことになるでしょう。借用規則により、 そのような場面に落ち着かないよう回避されるのです。
注釈:
Vec<T>
の実装に関する詳細については、https://doc.rust-lang.org/stable/nomicon/vec.htmlの、 "The Rustonomicon"を参照されたし。
ベクタの値を走査する
ベクタの要素に順番にアクセスしたいなら、添え字で1回に1要素にアクセスするのではなく、全要素を走査することができます。
リスト8-8でfor
ループを使い、i32
のベクタの各要素に対する不変な参照を得て、それらを出力する方法を示しています。
# #![allow(unused_variables)] #fn main() { let v = vec![100, 32, 57]; for i in &v { println!("{}", i); } #}
リスト8-8: for
ループで要素を走査し、ベクタの各要素を出力する
全要素に変更を加える目的で、可変なベクタの各要素への可変な参照を走査することもできます。
リスト8-9のfor
ループでは、各要素に50
を足しています。
# #![allow(unused_variables)] #fn main() { let mut v = vec![100, 32, 57]; for i in &mut v { *i += 50; } #}
リスト8-9: ベクタの要素への可変な参照を走査する
可変参照が参照している値を変更するには、+=
演算子を使用する前に、
参照外し演算子(*
)を使用してi
の値に辿り着かないといけません。
Enumを使って複数の型を保持する
この章の冒頭で、ベクタは同じ型の値しか保持できないと述べました。これは不便に考えられることもあります; 異なる型の要素を保持する必要性が出てくるユースケースも確かにあるわけです。幸運なことに、 enumの列挙子は、同じenumの型の元に定義されるので、ベクタに異なる型の要素を保持する必要が出たら、 enumを定義して使用することができます!
例えば、スプレッドシートの行から値を得たくなったとしましょう。ここで行の列には、整数を含むものや、 浮動小数点数を含むもの、文字列を含むものがあります。列挙子が異なる値の型を保持するenumを定義できます。 そして、このenumの列挙子は全て同じ型: enumの型と考えられるわけです。それからそのenumを保持するベクタを生成でき、 結果的に異なる型を保持できるようになるわけです。リスト8-10でこれを模擬しています。
# #![allow(unused_variables)] #fn main() { enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ]; #}
リスト8-10: enum
を定義して、一つのベクタに異なる型の値を保持する
各要素を保持するのにヒープ上でズバリどれくらいのメモリが必要になるかをわかるように、
コンパイラがコンパイル時にベクタに入る型を知る必要があります。副次的な利点は、
このベクタではどんな型が許容されるのか明示できることです。もしRustでベクタがどんな型でも保持できたら、
ベクタの要素に対して行われる処理に対して一つ以上の型がエラーを引き起こす可能性があったでしょう。
enumに加えてmatch
式を使うことは、第6章で議論した通り、コンパイル時にありうる場合全てに対処していることをコンパイラが、
確認できることを意味します。
プログラム記述時にプログラムがベクタに実行時に保持するありとあらゆる一連の型をプログラマが知らない場合、 このenumテクニックはうまく動かないでしょう。代わりにトレイトオブジェクトを使用することができ、こちらは第17章で解説します。
今や、ベクタを使用するべきありふれた方法について議論したので、標準ライブラリでVec<T>
に定義されている多くの有益なメソッドについては、
APIドキュメントを確認することを心得てください。例として、push
に加えて、pop
メソッドは最後の要素を削除して返します。
次のコレクション型に移りましょう: String
です!