(-> % read write unlearn)

All opinions expressed are solely my own and do not express the views or opinions of my employer.

rust-lang#05 動的で拡張可能な「Vector」

引き続き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)です。

vectorvec!マクロを使って作成します。

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
}

getget_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を、という意味です。