符号付き整数型と符号なし整数型
今まで,整数型として i32
や i64
が登場しました.ここで,整数型についてまとめておきます.
整数型には,符号付き整数型と符号なし整数型があります.どちらも
符号付き整数型の名前は i
で始まり,符号なし整数型の名前は u
で始まります.表にすると,次のようになります.
型 | 符号 | ビット長/バイト長 | 最小値 | おおよその値 | 最大値 | おおよその値 |
---|---|---|---|---|---|---|
i8 |
付き |
|
||||
i16 |
付き |
|
||||
i32 |
付き |
|
||||
i64 |
付き |
|
||||
i128 |
付き |
|
||||
u8 |
なし |
|
||||
u16 |
なし |
|
||||
u32 |
なし |
|
||||
u64 |
なし |
|
||||
u128 |
なし |
|
さらに, isize
と usize
という整数型があります.これは, 32 ビット環境では 32 ビット整数,64 ビット環境では 64 ビット整数になります.つまり 64 ビット環境であれば isize
と i64
の中身は同じです.
ただし,中身が同じだからといって,たとえば i64
型の式が isize
型の変数に代入できるわけではありません.次のコードは,たとえ 64 ビット環境であってもエラーになります.
fn main() {
let x: isize = 1i64;
}
このコードは, 64 ビット環境ではうまく動くかもしれませんが, 32 ビット環境では i64
型の値を i32
型の変数に代入することになってしまい,問題が生じます.このように,ある環境では動くが別の環境では動かないようなコードが生まれることを防ぐため, isize
と i32
/ 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]
という形式で表されます.このとき, N
は usize
型の定数[1]でなければなりません(今回の例では,整数リテラル 5
が usize
型と推論されています).
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 の略).これを「範囲外アクセス」といいます.
-
「定数」という言葉は今までに出てきていませんが,今までに登場したものだと整数リテラルが定数に属します. ↩︎