Chapter 12

配列

符号付き整数型と符号なし整数型

今まで,整数型として i32i64 が登場しました.ここで,整数型についてまとめておきます.

整数型には,符号付き整数型符号なし整数型があります.どちらも n ビットで 2^n 通りの整数を表せますが,その範囲が異なります.n ビットの符号付き整数型で表せるのは, -2^{n - 1} 以上 2^{n - 1} 未満の整数です.一方, n ビットの符号なし整数型で表せるのは, 0 以上 2^n 未満の整数です.

符号なし整数型では,非負整数がそのまま 2 進法で表現されます.一方,符号付き整数型では,負の数が 2 の補数として表現され,最上位の 1 ビットが符号になります.

符号付き整数型の名前は i で始まり,符号なし整数型の名前は u で始まります.表にすると,次のようになります.

符号 ビット/バイト 最小値 おおよその値 最大値 おおよその値
i8 付き 8 / 1 -2^7 = -128 2^7 - 1 = 127
i16 付き 16 / 2 -2^{15} \approx -3.27\times10^4 2^{15} - 1 \approx 3.27\times10^4
i32 付き 32 / 4 -2^{31} \approx -2.15\times10^9 2^{31} - 1 \approx 2.15\times10^9
i64 付き 64 / 8 -2^{63} \approx -9.22\times10^{18} 2^{63} - 1 \approx 9.22\times10^{18}
i128 付き 128 / 16 -2^{127} \approx -1.70\times10^{38} 2^{127} - 1 \approx 1.70\times10^{38}
u8 なし 8 / 1 0 2^8 - 1 = 255
u16 なし 16 / 2 0 2^{16} - 1 \approx 6.55\times10^4
u32 なし 32 / 4 0 2^{32} - 1 \approx 4.29\times10^9
u64 なし 64 / 8 0 2^{64} - 1 \approx 1.84\times10^{19}
u128 なし 128 / 16 0 2^{128} - 1 \approx 3.40\times10^{38}

i は integer (整数)の頭文字, u は unsigned (符号なし)の頭文字です.

さらに, isizeusize という整数型があります.これは, 32 ビット環境では 32 ビット整数64 ビット環境では 64 ビット整数になります.つまり 64 ビット環境であれば isizei64 の中身は同じです.

ただし,中身が同じだからといって,たとえば i64 型の式が isize 型の変数に代入できるわけではありません.次のコードは,たとえ 64 ビット環境であってもエラーになります.

fn main() {
    let x: isize = 1i64;
}

このコードは, 64 ビット環境ではうまく動くかもしれませんが, 32 ビット環境では i64 型の値を i32 型の変数に代入することになってしまい,問題が生じます.このように,ある環境では動くが別の環境では動かないようなコードが生まれることを防ぐため, isizei32 / i64 はあくまで別の型として扱われます.

配列

配列とは,同じ型の変数を複数個まとめて扱えるようにしたものです.

fn main() {
    let array: [i64; 5];
    array = [3, 7, 31, 127, 8191];
    assert_eq!(array[0], 3_i64);
    assert_eq!(array[1], 7_i64);
    assert_eq!(array[2], 31_i64);
    assert_eq!(array[3], 127_i64);
    assert_eq!(array[4], 8191_i64);
}

array という変数があり,型は [i64; 5] ,代入された値は [3, 7, 31, 127, 8191] となっています.

こうすると, array[0]array[1]array[2]array[3]array[4] という 5 つの i64 型変数が使えるようになります.

各要素の型を T,要素の個数を N とすると,配列の型は [T; N] という形式で表されます.このとき, Nusize 型の定数[1]でなければなりません(今回の例では,整数リテラル 5usize 型と推論されています).

2 つの配列型は, T が異なると別の型になります.たとえば [i32; 5][i64; 5] は別の型です.一方, N が異なっても別の型になります.たとえば [i64; 5][i64; 6] は別の型です.

配列の型を [T; N] とすると, array[0] array[1] array[2] …… array[N - 1] の型は全て T になります.今回は配列の型が [i64; 5] なので, array[0] array[1] array[2] …… array[4] の型は全て i64 になります(array = [3, 7, 31, 127, 8191]; の行では整数リテラル 3 7 31 127 8191 が全て i64 型に推論されていることに注意してください).

array[0] array[1] array[2] ……の値は,それぞれ括弧の中の 1 番目, 2 番目, 3 番目……の値になります.このときの 0 1 2 ……をインデックスといい,インデックスは usize 型の値である必要があります.

fn main() {
    let array = [1, 2, 3];
    assert_eq!(array[0usize], 1);
    assert_eq!(array[1], 2); // 整数リテラル 1 が usize 型に推論される
    // assert_eq!(array[2u32], 3); // インデックスが usize でないのでエラー
}

[3, 7, 31, 127, 8191] は,最後にもう一つカンマを付けて [3, 7, 31, 127, 8191,] としても同じです.

また, [57, 57, 57, 57, 57, 57, 57, 57, 57, 57] のように全ての要素が等しい配列は, [57; 10] と書くことができます.

fn main() {
    let array = [57; 10];
    assert_eq!(array[0], 57);
    assert_eq!(array[9], 57);
}

パターンマッチ

配列もパターンマッチが可能です.

fn main() {
    let [x, y, z] = [1, 2, 3];
    assert_eq!(x, 1);
    assert_eq!(y, 2);
    assert_eq!(z, 3);
}


x には 1, y には 2, z には 3 がそれぞれ代入されます.

配列とタプルの違い

「複数の変数をまとめる」という意味ではタプルと配列は同じですが,次のような違いがあります.

  • タプルは各要素の型が異なってもよいのに対し,配列は全ての要素が同じ型をもちます.
  • 配列の要素にアクセスするとき,インデックスは変数でもかまいません.
    use proconio::input;
    
    fn main() {
        let array = [0, 10, 20, 30, 40, 50];
        input! {
            index: usize,
        }
        let ans = array[index];
        println!("{}", ans);
    }
    
    array[0, 10, 20, 30, 40, 50] が代入されているので, array の型は [i32; 6] と推論されます.よって array の要素は全て i32 型なので, ans の型は i32 であると分かります.
    一方,次のコードは動きません.
    use proconio::input;
    
    fn main() {
        let tuple = (0, 10, 20, 30, 40, 50);
        input! {
            index: usize,
        }
        let ans = tuple.index; // tuple.0 や tuple.1 にはならない
        println!("{}", ans);
    }
    
    タプルの要素にアクセスするとき, . の後は単なる数字でなければなりません.

範囲外アクセス

上で登場したコードをもう一度見てみます.

use proconio::input;

fn main() {
    let array = [0, 10, 20, 30, 40, 50];
    input! {
        index: usize,
    }
    let ans = array[index];
    println!("{}", ans);
}

これは,標準入力としてたとえば 3 を与えて実行すると,変数 index の値が 3 になるため, array[3] の値 30 が ans に代入され,出力されます.

では,標準入力として 10 を与えたらどうなるでしょうか? array の長さは 6 しかないのに, array[10] にアクセスしようとしてしまっています.これは,次のような実行時エラーになります.

thread 'main' panicked at 'index out of bounds: the len is 6 but the index is 10', src/main.rs:8:15
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

index out of bounds: the len is 6 but the index is 10 は,「配列の長さは 6 だがインデックスは 10 であり,配列の範囲を超えている」という意味です(len は length の略).これを「範囲外アクセス」といいます.

脚注
  1. 「定数」という言葉は今までに出てきていませんが,今までに登場したものだと整数リテラルが定数に属します. ↩︎