引き続きRustの勉強ですねー。 前回の If と Loopsに続いてhttps://doc.rust-lang.org/bookをやっていきます。
今日は4.7. Vectorsからです。
4.7. Vectors
- vectorは動的で拡張可能なarrayです。
- 標準ライブラリ内で
Vec<T>
として実装されています。 - vectorはデータを常にヒープにアロケートします。
- 要素の取得の計算量は
O(1)
、push(末尾への追加)もO(1)
、pop(末尾から取り出して削除)もO(1)
です。
vectorはvec!
マクロを使って作成します。
let v1 = vec![1, 1, 1, 1, 1]; // 型はVec<i32>に推論される let v2: Vec<i32> = vec![1, 1, 1, 1, 1]; // 型を明示 // 初期値とサイズを指定する書き方もあります。上記のコードと同じ意味になります。 let v3 = vec![1; 5];
arrayだとどう書いたか復習。vectorと違って、型にサイズが入るのは静的なarrayならではです。
let a1 = [1, 1, 1, 1, 1]; let a2: [i32; 5] = [1, 1, 1, 1, 1]; let a3 = [1; 5];
ちなみに、マクロの引数を囲む括弧は()
でも[]
でもどっちでもいいみたいです。
慣例的に、vectorは[]
を使うもようです。
// 上記のコードと同じ let v1 = vec!(1, 2, 3, 4, 5); let v2: Vec<i32> = vec!(1, 2, 3, 4, 5); let v3 = vec!(1; 5);
このVec<T>
の<T>
はジェネリクスです。
Javaとかと似てますね。
vectorは、各Tのデータを連続したメモリ領域に確保します。 *1 そのため、Tのデータサイズがコンパイル時に分かっている必要があります。
コンパイル時にTのサイズが分からない場合にはポインタを格納することになります。
ポインタを格納する用にBox
という型が用意されています。
Accessing elements
要素へアクセスする[]
fn main() { let v = vec![1, 2, 3, 4, 5]; println!("v[2] は、3番目の要素を指します。その値は {} です。", v[2]); //=> v[2] は、3番目の要素を指します。その値は 3 です。 }
vectorの要素を取得するインデックス値(上記の場合は2
)は、usize
型でなければいけません。
i32
型とかはアウトです。コンパイル・エラーです。
fn main() { let v = vec![1, 2, 3, 4, 5]; let i: usize = 0; let j: i32 = 0; v[i]; // OK v[j]; // Conpile Error }
Out-of-bounds Access
範囲外の要素へのアクセスはpanic
を引き起こします。
fn main() { let v = vec![10, 20, 30]; // 3番目の要素 println!("{}", v[2]); //=> 30 // 4番目の要素:範囲外! println!("{}", v[3]); //=> thread '<main>' panicked at 'index out of bounds: the len is 3 but the index is 3', ../src/libcollections/vec.rs:1178 }
get
やget_mut
メソッドを使うとOption
でラップした値が返ります。
そして、match
文を使うことで、正常系と異常系の処理を安全かつ明瞭に書き分けられます。
fn main() { let v = vec![10, 20, 30]; // 3番目の要素 let e1: i32 = match v.get(2) { Some(x) => *x, None => 0, }; println!("{}", e1); //=> 30 // 4番目の要素:範囲外! let e2: i32 = match v.get(3) { Some(x) => *x, None => 0, }; println!("{}", e2); //=> 0 }
Scalaのパターンマッチとほぼ同じ構文ですね。
// これはscalaのコード。vectorの範囲外アクセス object Main extends App { val v = Vector(10, 20, 30) // 3番目の要素 println(v(2)) //=> 30 // 4番目の要素:範囲外! println(v(3)) //=> java.lang.IndexOutOfBoundsException: 3 }
// これもscalaのコード。vectorの要素を安全に取得 object Main extends App { val v = Vector(10, 20, 30) // 3番目の要素 val e1 = v.lift(2) match { case Some(x) => x case None => 0 } println(e1) //=> 30 // 4番目の要素:範囲外! val e2 = v.lift(3) match { case Some(x) => x case None => 0 } println(e2) //=> 0 }
そっくりですね。
違いは、Scalaがそれぞれの分岐をcase
で区切るのに対して、Rustは,
で区切るところぐらいか。ちなみに、Rustのmatch文では,
ケツカンマしても怒られなかったです。 配列の要素など、式や文を列挙する書き方において区切り文字として使われるカンマ(,)の後に何の要素も書かないこと。ケツカンマを書いていると、後から最後尾に要素を追加する際にカンマのつけ忘れをしにくい。
個人的にはカンマっていうトークンが見づらくて好きではないので、case
って語を強制するScalaの書き方のほうが好きです。
Iterating
ベクタのイテレーションには3種類の方法があります。
// 1 let mut v = vec![1, 2, 3]; for i in &v { println!("{} に対する参照。", i); }
// 2 let mut v = vec![1, 2, 3]; for i in &mut v { println!("{} に対するミュータブルな参照。", i); }
// 3 let mut v = vec![1, 2, 3]; for i in v { println!("vectorとその要素 {} のownershipを取得。", i); }
3番めのownershipを取得する形でのイテレーションを、複数回おこなうことはできません。ownershipを取得しなければ何度でも参照を取れます。
fn main() { let mut v1 = vec![1, 2, 3]; for i in &v1 { println!("{}", i); } // No problem for i in &v1 { println!("{}", i); } // No problem let mut v2 = vec![1, 2, 3]; for i in &mut v2 { println!("{}", i); } // No problem for i in &mut v2 { println!("{}", i); } // No problem // No problem let mut v3 = vec![1, 2, 3]; for i in v3 { println!("{}", i); } // No problem for i in v3 { println!("{}", i); } // Compile Error //=> src/main.rs:12:14: 12:16 error: use of moved value: `v3` [E0382] //=> src/main.rs:12 for i in v3 { println!("{}", i); } //=> ^~ //=> src/main.rs:12:14: 12:16 help: run `rustc --explain E0382` to see a detailed explanation //=> src/main.rs:11:14: 11:16 note: `v3` moved here because it has type `collections::vec::Vec<i32>`, which is non-copyable //=> src/main.rs:11 for i in v3 { println!("{}", i); } }
ざっくりまとめ
fn main() { // vectorの作成 let mut v1 = Vec::new(); // push()は末尾に追加 v1.push(10); // [10] v1.push(20); // [10, 20] v1.push(30); // [10, 20, 30] // len()は要素数 assert_eq!(v1.len(), 3); // 添字アクセスで値を取得 assert_eq!(v1[2], 30); assert_eq!(v1.len(), 3); // 添字アクセスで値を更新 v1[2] = 300; // [10, 20, 300] assert_eq!(v1[2], 300); // pop()は末尾からOption型で取り出し assert_eq!(v1.pop(), Some(300)); // [10, 20] // pop()で取り出した要素はvectorからなくなる assert_eq!(v1.len(), 2); // vec!マクロを使えば初期値を指定してvectorが作成できる let v2 = vec![10, 10, 10]; // vec!マクロを使ったもう1つの初期化 let v3 = vec![10; 3]; assert_eq!(v2, v3); }
参考: vectorのAPIドキュメント
*1:一連のデータ全てを隣り合わせた連続したメモリ領域に確保するという意味ではなくて、1つ1つのTを、という意味です。