Rustのデータ型を徹底解説!スカラー型から複合型まで
こんにちは!今日も、The Rust Programming Language 日本語版 (通称 “The Book”) を参考に、Rustの学習を進めていきます。
今回は、The Bookの「データ型」の章を読み進めていきます。
(3.2. データ型 – The Rust Programming Language 日本語版)
Rustは静的型付け言語
Rustは静的型付け言語です。
これは、コンパイル時に全ての変数の型が判明している必要がある、ということです。
動的型付け言語に慣れていると、最初は少し戸惑うかもしれません。
しかし、普段プログラムを書く際に「この変数には数値を入れる」「この変数には文字列を入れる」と、頭の中で自然に型を意識しているはずです。
Rustでは、それを明示的にコードに記述する、と考えれば、それほど難しくないでしょう。
Rustでは変数を宣言する際に必ずしも型注釈は必要ではありません。
コンパイラは初期値から型を推論できます。
しかし、初期値がない場合や、型を明示したい場合は型注釈が必要です。
スカラー型
スカラー型は、単独の値を表す型です。Rustには、主に4つのスカラー型があります。
- 整数型
- 浮動小数点数型
- 論理値型
- 文字型
整数型 (Integer)
整数型は、小数部分のない数値です。符号付き (signed) と符号なし (unsigned) があります。
- 符号付き整数型:
i8
,i16
,i32
,i64
,i128
,isize
- 符号なし整数型:
u8
,u16
,u32
,u64
,u128
,usize
isize
と usize
は、プログラムが動作するコンピュータのアーキテクチャ (32ビット or 64ビット) に依存します。
どの整数型を使うべきか迷ったら、Rustのデフォルトである i32
を使うのがおすすめです。多くの場合、最速の型になります。
浮動小数点数型 (Floating-point number)
浮動小数点数型は、小数点以下の値を持つ数値を表します。Rustには、f32
(単精度) と f64
(倍精度) の2種類があります。
Rustのデフォルトは f64
です。現代のCPUでは、f32
と f64
の速度はほぼ同じで、f64
の方が精度が高いためです。
数値演算については他の言語と大きく変わりません。
ここで、The Bookには書かれていなかったけど、気になったことがあったので実験してみました。
「ゼロで割ったらどうなるんだろう?」
fn main(){
const x: f64 = 4.0;
const y: f64 = 3.0;
const z: f64 = 0.0;
println!("{}", x / z);
}
//出力結果「inf」
浮動小数点型 (f64
) でゼロ除算を行うと、結果は inf
(無限大) になりました。
Pythonだと ZeroDivisionError
になるので、ちょっと意外ですね。
「じゃあ、整数型だとどうなるんだろう?」と思って、AIに聞いてみました。
私: 浮動小数点型だとゼロ除算でinfになったけど、整数型だとどうなるの?
AI: 整数型でゼロ除算を行うと、パニックします。
「パニック」というのは、Rustのエラー処理の仕組みの一つで、回復不能なエラーが発生した時にプログラムが停止することです。
fn main(){
const x: isize = 4;
const y: isize = 3;
const z: isize = 0;
println!("{}", x / z);
}
/*
error: this operation will panic at runtime
--> Main.rs:6:20
|
6 | println!("{}", x / z);
| ^^^^^^ attempt to divide `4_isize` by zero
|
= note: `#[deny(unconditional_panic)]` on by default
*/
実際に試してみると、コンパイルは通るものの、実行時にエラーが発生してプログラムが停止しました。
この辺はさっくりと実験しただけなので詳しく試すと違う結果になるかもしれませんね。
論理値型 (Boolean)
論理値型 (bool
) は、true
または false
のいずれかの値を取ります。
let t = true;
let f: bool = false; // 明示的な型注釈
条件分岐など、プログラムの制御フローでよく使われます。
文字型 (Character)
文字型 (char
) は、Unicodeスカラー値を表します。シングルクォーテーション ('
) で囲んで表現します。
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
文字列 (String
) はダブルクォーテーション ("
) で囲みます。文字列については、後の章で詳しく解説されます。
複合型
複合型は、複数の値を1つの型にまとめることができる型です。Rustには、タプルと配列の2つの複合型があります。
タプル型 (Tuple)
タプルは、異なる型の値をまとめて1つの複合値にする型です。丸括弧 (()
) の中に、カンマ区切りの値リストを記述します。
let tup: (i32, f64, u8) = (500, 6.4, 1);
タプルの各要素には、ピリオド (.
) の後にインデックスを指定してアクセスできます。
let x = (100, 10, 1);
println!("{}", x.1); // 出力: 10
タプルは、型注釈がなくともコンパイラが与えられた値から自動的に最適な型を推論します。
ここで、タプルの存在しない要素にアクセスしようとするとどうなるか、試してみました。
fn main(){
let x = (100, 10, 1);
println!("{}", x.4);
}
すると、コンパイル時にエラーが発生しました。
error[E0609]: no field `4` on type `({integer}, {integer}, {integer})` --> Main.rs:3:22 | 3 | println!("{}", x.4); | ^ error: aborting due to previous error For more information about this error, try `rustc --explain E0609`.
E0609
エラーは、存在しないタプルのフィールドにアクセスしようとした時に発生するエラーです。
Rustでは、このようにコンパイル時にエラーを検出してくれるため、実行時に予期せぬエラーが発生するリスクを減らすことができます。
配列型 (Array)
配列は、同じ型の複数の値をまとめて扱う型です。Rustの配列は、固定長です。一度宣言すると、サイズを変更できません。
let a = [1, 2, 3, 4, 5];
配列の型は、要素の型と要素数を角括弧 ([]
) で囲んで表現します。
let a: [i32; 5] = [1, 2, 3, 4, 5]; // i32型、要素数5
同じ値で初期化された配列は、次のように簡潔に記述できます。
let a = [3; 5]; // [3, 3, 3, 3, 3] と同じ
配列の要素には、角括弧 ([]
) の中にインデックスを指定してアクセスします。
let a = [3, 2, 1, 0];
println!("{}", a[0]); // 出力: 3
配列の範囲外のインデックスにアクセスしようとすると、コンパイル時にエラーが検出される場合もありますし、
コンパイルが通っても実行時にエラー (パニック) が発生する場合もあります。
Rustでは、配列の境界チェックが行われるため、無効なメモリへのアクセスが防止され、安全性が保たれます。
なお、標準ライブラリには、可変長の配列に似た機能を持つ ベクタ型 (Vec<T>
) があります。
どちらを使うべきか迷ったら、ベクタ型を使うのがおすすめです。
今回の学習を終えて
今回は、The Rust Programming Language 日本語版の「データ型」の章を読み、Rustの基本的なデータ型について学びました。
静的型付け言語であるRustのデータ型は、Pythonなどの動的型付け言語とは異なる点が多く、最初は戸惑うかもしれません。
しかし、「この変数にはどんな種類の値が入るのか」を意識することは、バグの少ない、安全なコードを書く上で非常に重要です。
次回は、「関数」の章に進む予定です。
今回もAIにたくさん助けてもらいながら学習を進められました。