【Rust入門】基本型を分かりやすく解説(スカラー型と複合型)

Rustを学び始めると、最初に出てくるのが基本型(primitive types)です。Rustは静的型付け言語のため、全ての変数には型が存在します。この記事では、Rustの基本型を「スカラー型」と「複合型」に分けて分かりやすく解説します。
Rustの基本型とは?
Rust の基本型(primitive types)とは、プログラムを実装する上で頻繁に使われる、言語に組み込まれた型のことです。他のプログラミング言語でも同様の型が出てきますが、Rustでも大きく分けて以下の2つに分類される基本型があります。
- スカラー型(scalar types):1つの値を表す型
- 複合型(compound types):複数の値をまとめて扱う型
以降では、それぞれの型に分類される各型について詳細に見ていきましょう。
スカラー型
スカラー型(scalar types)は、1つの値を表す型で、整数型、浮動小数点型、論理型、文字型があります。
整数型
整数型(integer types)は、小数点を含まない数値を表します。符号付きと符号なしの型があり用途に応じて選びます。例えば、符号付き i8
型の整数の値の範囲は -2^7 ~ 2^7-1 (-128 ~ 127)
で、符号なし u8
型の整数の値の範囲は 0 ~ 2^8-1 (0 ~ 255)
となります。
ビット | 符号付き 型名 | 符号なし 型名 |
---|---|---|
8 ビット | i8 | u8 |
16 ビット | i16 | u16 |
32 ビット | i32 | u32 |
64 ビット | i64 | u64 |
128 ビット | i128 | u128 |
アーキテクチャ依存 (32 or 64 ビット) | isize | usize |
なお、isize
や usize
については、使用しているアーキテクチャに依存して 32bit なのか 64bit なのかが決まります。
fn main() { let x = 100; // デフォルトでは、i32と推論される let y: u32 = 200; // 符号なし32ビット整数(u32)を指定する println!("xの値: {}", x); println!("yの値: {}", y); }
【実行結果】 xの値: 100 yの値: 200
変数の後ろの : u32
という部分は型注釈と言います。Rustは、型推論が強力な言語であるため、基本的に型を指定しなくても入力された値に従って型が推論されます。整数型の場合には、基本的に選ばれるデフォルト型は i32
型です。
浮動小数点型
浮動小数点型(floating-point types)は、小数の数値を表します。小数では、単精度浮動小数点である f32
型と倍精度浮動小数点である f64
型があります。
ビット | 型名 | 備考 |
---|---|---|
32 ビット | f32 | 単精度浮動小数点 |
64 ビット | f64 | 倍精度浮動小数点 |
他のプログラミング言語では、単精度 (float
)、倍精度 (double
)という型を採用する言語が多いですが、Rustでは double
はないので注意しましょう。f64
型が他言語でいうところの double
になります。
fn main() { let x = 2.5; // デフォルトでは、f64と推論される let y: f32 = 1.2; // 単精度浮動小数点(f32)を指定する println!("xの値: {}", x); println!("yの値: {}", y); }
【実行結果】 xの値: 2.5 yの値: 1.2
浮動小数点型で、基本的に選ばれるデフォルト型は f64
型です。
論理型
論理型(boolean)は、真か偽かを表現する型で、true または false の2つの値を取ります。
fn main() { let is_valid = true; // 真 let is_invalid: bool = false; // 偽 println!("is_validの値: {}", is_valid); println!("is_invalidの値: {}", is_invalid); }
【実行結果】 is_validの値: true is_invalidの値: false
論理型は、条件分岐や繰り返し条件などの判断に非常に重要な型です。
文字型
文字型(char)は、Unicodeのスカラー値 (4バイト) の1文字分表す型です。1文字を表す場合には、シングルクォート 'A'
のように記述する必要があります。ASCIIだけでなく、絵文字や漢字なども含めてあらゆる Unicode 文字を1文字として扱うことが可能となります。
fn main() { let c = 'A'; // 1文字を扱う let emoji: char = '😊'; // 絵文字も対応可能 println!("cの値: {}", c); println!("emojiの値: {}", emoji); }
【実行結果】 cの値: A emojiの値: 😊
上記のように char 型は、1文字を扱うための型です。Rustで文字列を扱う場合には、String
型を使用します。String
型については、また別記事で扱おうと思いますので、ここでは異なるものとして覚えておいてもらえればと思います。
fn main() { let c: char = 'A'; // 1文字 (シングルクォート) let s: String = String::from("Hello!"); // 文字列 (ダブルクォート) println!("cの値: {}", c); println!("sの値: {}", s); }
【実行結果】 cの値: A sの値: Hello!
複合型
複合型には、複数の値をまとめて表現できる型で、タプルと配列があります。
タプル
タプル(tuple)は、複数の異なる型の値をひとまとまりにできる型です。タプルの中に含める型の順番と個数が重要な要素になり、一致しないようなデータは作れません。
fn main() { let info: (i32, f64, bool) = (100, 0.123, true); // 型の順番と個数が重要 let (i_val, f_val, flag) = info; // タプルに対するパターンマッチによる値の束縛 (デストラクチャリング) // タプルへのアクセス println!("infoの値: {:?}", info); println!("1つ目の要素: {}", info.0); println!("2つ目の要素: {}", info.1); println!("3つ目の要素: {}", info.2); // println!("エラーになる: {}", info.3); // デストラクチャリングの結果を表示 println!("i_valの値: {}", i_val); println!("f_valの値: {}", f_val); println!("flagの値: {}", flag); }
【実行結果】 infoの値: (100, 0.123, true) 1つ目の要素: 100 2つ目の要素: 0.123 3つ目の要素: true i_valの値: 100 f_valの値: 0.123 flagの値: true
上記の例では、info
という名前のタプルを作成していますが、型の順序は i32
、f64
、bool
という順になっています。値の型の順序が一致していないとコンパイルエラーとなります。
i_val
、f_val
、flag
変数に値を設定しているように、タプルでは異なる変数にタプル内部の要素をパターンマッチさせて束縛することができます。これを「デストラクチャリング(destructuring)」と呼びます。
タプルの要素にアクセスするには、info.0
のように、ドット(.
)を使用して要素位置の数値を指定します。数値は 0
からなので注意してください。また、上記例ではコメントアウトしていますが、info.3
のようにタプルの範囲外の要素にアクセスしようとするとコンパイルエラーになります。
{}
と {:?}
上記例で println!
の際に {}
と {:?}
という記載があります。これは値を埋め込むためのものですが、タプルの変数に対して {}
で表示しようとするとエラーになります。
これは、タプルが {}
に値を設定するための Display
トレイトを実装していないからです。一方で、Debug
トレイトを実装しているため {:?}
で表示ができます。
トレイトとは、型の振る舞いを定義するための仕組みです。別途トレイトについては取り上げてみたいと思います。
配列型
配列(array)は、同じ型の値を固定長で格納するための型で、要素数は固定で定義したら変更できません。タプルのように、さまざまな種類の型を含めることはできませんので注意が必要です。
fn main() { let scores = [1, 2, 3]; // 推論される型は [i32; 3] (i32の要素を3つ持つ配列) let zeros: [i32; 5] = [0; 5]; // 要素0を5つ繰り返す配列 [0, 0, 0, 0, 0] // 配列へのアクセス println!("scoresの値: {:?}", scores); println!("zerosの値: {:?}", zeros); // 配列の要素へのアクセス println!("scores[0]: {}", scores[0]); println!("scores[1]: {}", scores[1]); println!("scores[2]: {}", scores[2]); // println!("エラーになる: {}", scores[3]); }
【実行結果】 scoresの値: [1, 2, 3] zerosの値: [0, 0, 0, 0, 0] scores[0]: 1 scores[1]: 2 scores[2]: 3
配列の型は、[型; 長さ]
という形式で定義します。また、上記の zeros の例のように [値; 個数]という記載をすることで、ある値を個数だけ繰り返した配列を作ることが可能です。
配列の要素にアクセスするには、scores[0]
のように、角括弧([]
)を使用して要素位置の数値を指定します。数値は 0
からなので注意してください。また、タプルの例と同様で上記例ではコメントアウトしていますが、scores[3]
のように配列の範囲外の要素にアクセスしようとするとコンパイルエラーになります。
型注釈と型推論
Rustでは、let x: u32 = 100;
のように変数に明示的に型を書くことができます。この「: u32
」の部分は型注釈(type annotation)と言います。ただし、多くの場合、コンパイラが設定されている数値をもとに該当の型を型推論(type inference)してくれるため、型注釈を記載する必要はありません。
基本的には、以下のスタンスでよいと思います。
- ローカル変数は、基本は型推論に任せて型注釈をつける必要はない。
- 読み手に意図を伝えたい場合には型注釈をつける。
- 関数の引数と戻り値には型注釈は必須。
Rust は、信頼できるところはコンパイラに任せて、判断が必要なところは開発者が責任を持つことで、開発効率と安全性のバランスがとられています。
まとめ
この記事では、Rustの基本型について「スカラー型」と「複合型」に分けて解説しました。
- スカラー型は、1つの値を表すための型で、整数型(符号付き / 符号なし)、浮動小数点型、論理型、文字型があります。
- 複合型には、異なる型をまとめられるタプルと同じ型の値を固定長で保持する配列があります。
- Rust の型システムは非常に堅牢であるため、型推論の柔軟さと型注釈による安全性のバランスが取れます。意図の明示が重要な部分では型注釈は必要ですが、明確な場面では型を省略して効率よく開発ができます。
Rust を学ぶ上で基本型をはじめとした型の理解は避けて通れません。まずは、基本型をしっかり押さえておきましょう。