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

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

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

符号付き整数型の名前は 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}

さらに, 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 型変数が使えるようになります.タプルと同じように,今回も 1, 2, 3……ではなく 0, 1, 2……と数えていることに注意してください.

各要素の型を 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[3] 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 でないのでエラー
}

また, [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 がそれぞれ代入されます.

配列とタプルの違い

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

  • タプルは各要素の型が異なってもよいのに対し,配列は全ての要素が同じ型をもちます.タプル (i32, f64).0 の型が i32.1 の型が f64 ですが,[0] の型が i32[1] の型が f64 であるような配列は作れません.
  • 配列の要素にアクセスするとき,インデックスの部分には式が書けます.
    use proconio::input;
    
    fn main() {
        let array = [0, 10, 20, 30, 40, 50];
        input! {
            index: usize,
        }
        let ans = array[index - 1];
        println!("{}", ans);
    }
    
    array[index - 1] において,[] の中で変数 index を使った式を書いています.ここで変数 ans の型がどう推論されるか考えてみましょう.まず array[0, 10, 20, 30, 40, 50] が代入されているので, array の型は [i32; 6] と推論されます.よって array の要素は全て i32 型なので,index の値が何であろうと array[index - 1] の型は 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);
    }
    
    仮にこのようなことができたとすると,tuple の型がたとえば (i32, f64) だった場合に ans の型が決まらなくなってしまいますね.コンパイルの段階で全ての変数の型が決まらなければいけないことを思い出しましょう.
    よって,タプルの要素にアクセスするとき, . の後は単なる数字でなければなりません.

範囲外アクセス

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

use proconio::input;

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

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

では,標準入力として 10 を与えたらどうなるでしょうか? array の長さは 6 しかないのに, array[9] にアクセスしようとしてしまっています.このような場合,パニックが起こります.

thread 'main' panicked at 'index out of bounds: the len is 6 but the index is 9', 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 9 は,「配列の長さは 6 だがインデックスは 9 であり,配列の範囲を超えている」という意味です(len は length の略).これを「範囲外アクセス」といいます.

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