第7章: 型システム
学習目標
- Rustの型システムの基本を理解する
- プリミティブ型と複合型を使いこなす
- 型推論の仕組みを学ぶ
- 型変換とキャストの方法を把握する
---
7.1 Rustの型システム
7.1.1 静的型付け
Rustは静的型付け言語です:
fn main() {
let x: i32 = 5; // 型注釈
let y = 10; // 型推論(i32)
// let z = x + "hello"; // ❌ コンパイルエラー!型不一致
}
特徴:
- コンパイル時に型チェック
- 実行時のオーバーヘッドなし
- 型安全性の保証
7.1.2 型推論
コンパイラが多くの型を推論します:
fn main() {
let x = 5; // i32と推論
let y = 3.14; // f64と推論
let s = "hello"; // &strと推論
let v = vec![1, 2, 3]; // Vec<i32>と推論
}
推論のルール:
┌─────────────────────────────────────────────────────────┐
│ 型推論のプロセス │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. リテラルから推論 │
│ 42 → i32 │
│ 3.14 → f64 │
│ true → bool │
│ │
│ 2. 使用方法から推論 │
│ let x = vec![]; │
│ x.push(5); → Vec<i32> │
│ │
│ 3. 関数の返り値から推論 │
│ let x = String::new(); → String │
│ │
└─────────────────────────────────────────────────────────┘
---
7.2 プリミティブ型
7.2.1 整数型
符号付き整数:
let a: i8 = -128; // -128 ~ 127
let b: i16 = -32768; // -32,768 ~ 32,767
let c: i32 = -2147483648; // -2^31 ~ 2^31-1(デフォルト)
let d: i64 = -9223372036854775808;
let e: i128 = 0; // -2^127 ~ 2^127-1
let f: isize = 0; // ポインタサイズ(32/64bit)
符号なし整数:
let a: u8 = 255; // 0 ~ 255
let b: u16 = 65535; // 0 ~ 65,535
let c: u32 = 4294967295; // 0 ~ 2^32-1
let d: u64 = 0;
let e: u128 = 0; // 0 ~ 2^128-1
let f: usize = 0; // ポインタサイズ
整数リテラルの表記:
let decimal = 98_222; // 10進数(_は読みやすさのため)
let hex = 0xff; // 16進数
let octal = 0o77; // 8進数
let binary = 0b1111_0000; // 2進数
let byte = b'A'; // バイトリテラル(u8のみ)
型サフィックス:
let x = 42u32; // u32型
let y = 10i64; // i64型
let z = 100_000u64; // u64型
7.2.2 浮動小数点数型
let x: f32 = 3.14; // 32ビット浮動小数点数
let y: f64 = 2.71828; // 64ビット浮動小数点数(デフォルト)
// 科学的記法
let big = 1.23e10; // 1.23 × 10^10
let small = 1.23e-10; // 1.23 × 10^-10
注意点:
fn main() {
let x = 0.1 + 0.2;
println!("{}", x); // 0.30000000000000004(浮動小数点数の誤差)
// 比較には注意
assert_ne!(x, 0.3); // ✅
// assert_eq!(x, 0.3); // ❌ パニック
}
7.2.3 真偽値型
let t: bool = true;
let f: bool = false;
// 比較演算の結果
let is_greater = 5 > 3; // true
let is_equal = 5 == 5; // true
7.2.4 文字型
let c: char = 'z';
let emoji: char = '😀';
let katakana: char = 'ア';
// charは4バイト(Unicode Scalar Value)
assert_eq!(std::mem::size_of::<char>(), 4);
文字とバイトの違い:
let c: char = '文'; // 4バイト
let b: u8 = b'A'; // 1バイト(ASCIIのみ)
---
7.3 複合型
7.3.1 タプル型
複数の値を1つにまとめる:
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 分配
let (x, y, z) = tup;
println!("x = {}, y = {}, z = {}", x, y, z);
// インデックスアクセス
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;
}
ユニット型:
let unit: () = (); // 空のタプル
関数が値を返さない場合の返り値型:
fn do_something() -> () {
println!("何かする");
}
// 省略形
fn do_something() {
println!("何かする");
}
7.3.2 配列型
固定長の同じ型の値の集まり:
fn main() {
// 型注釈: [型; 長さ]
let a: [i32; 5] = [1, 2, 3, 4, 5];
// 同じ値で初期化
let b = [3; 5]; // [3, 3, 3, 3, 3]
// アクセス
let first = a[0];
let second = a[1];
// 範囲外アクセスはパニック
// let out_of_bounds = a[10]; // ❌ ランタイムパニック
}
配列 vs ベクタ:
┌─────────────────────────────────────────────────────────┐
│ 配列 vs ベクタ │
├─────────────────────────────────────────────────────────┤
│ │
│ 配列 [T; N] │
│ - 固定長 │
│ - スタック上 │
│ - コンパイル時にサイズが既知 │
│ │
│ ベクタ Vec<T> │
│ - 可変長 │
│ - ヒープ上 │
│ - ランタイムでサイズ変更可能 │
│ │
└─────────────────────────────────────────────────────────┘
7.3.3 スライス型
配列やベクタの一部への参照:
fn main() {
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3]; // [2, 3]
println!("{:?}", slice);
}
文字列スライス:
fn main() {
let s = String::from("hello world");
let hello: &str = &s[0..5];
let world: &str = &s[6..11];
println!("{}, {}", hello, world);
}
---
7.4 型エイリアス
7.4.1 type キーワード
既存の型に新しい名前を付ける:
type Kilometers = i32;
type Point = (f64, f64);
type Result<T> = std::result::Result<T, String>;
fn main() {
let distance: Kilometers = 100;
let origin: Point = (0.0, 0.0);
}
用途:
// 長い型名を短縮
type BoxedFn = Box<dyn Fn(i32) -> i32>;
// 特定の用途を明確化
type UserId = u64;
type UserName = String;
fn create_user(id: UserId, name: UserName) {
// ...
}
---
7.5 型推論の詳細
7.5.1 推論の限界
型推論には限界があります:
fn main() {
let numbers = vec![1, 2, 3]; // Vec<i32>と推論
// let x = numbers.iter().collect(); // ❌ エラー!型が不明
let x: Vec<_> = numbers.iter().collect(); // ✅ Vec<&i32>
}
7.5.2 ターボフィッシュ構文
ジェネリック関数で型を明示:
fn main() {
let numbers = "1 2 3 4";
// ターボフィッシュ ::<>
let parsed: Vec<i32> = numbers
.split_whitespace()
.map(|s| s.parse::<i32>().unwrap())
.collect();
println!("{:?}", parsed);
}
別の書き方:
let parsed = numbers
.split_whitespace()
.map(|s| s.parse().unwrap())
.collect::<Vec<i32>>();
---
7.6 型変換
7.6.1 as キャスト
プリミティブ型間の変換:
fn main() {
let x = 42i32;
let y = x as f64; // i32 → f64
let z = y as u8; // f64 → u8
println!("x = {}, y = {}, z = {}", x, y, z);
}
注意点:
fn main() {
let x = 1000i32;
let y = x as u8; // 232(オーバーフロー)
println!("{}", y); // 1000 % 256 = 232
}
7.6.2 From/Into トレイト
安全な型変換:
fn main() {
// From
let s = String::from("hello");
let num = i32::from(5u8);
// Into(Fromの逆)
let s: String = "hello".into();
}
カスタム型への実装:
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(value: i32) -> Self {
Number { value }
}
}
fn main() {
let num = Number::from(42);
let num2: Number = 100.into();
println!("{}, {}", num.value, num2.value);
}
7.6.3 TryFrom/TryInto トレイト
失敗する可能性のある変換:
use std::convert::TryFrom;
use std::convert::TryInto;
fn main() {
// TryFrom
let big_number = 1000i32;
let small_number: Result<u8, _> = u8::try_from(big_number);
match small_number {
Ok(n) => println!("変換成功: {}", n),
Err(e) => println!("変換失敗: {}", e),
}
// TryInto
let result: Result<u8, _> = big_number.try_into();
}
---
7.7 型のサイズ
7.7.1 size_of
型のメモリサイズを取得:
use std::mem;
fn main() {
println!("i8: {} bytes", mem::size_of::<i8>()); // 1
println!("i32: {} bytes", mem::size_of::<i32>()); // 4
println!("i64: {} bytes", mem::size_of::<i64>()); // 8
println!("f64: {} bytes", mem::size_of::<f64>()); // 8
println!("bool: {} bytes", mem::size_of::<bool>()); // 1
println!("char: {} bytes", mem::size_of::<char>()); // 4
println!("&i32: {} bytes", mem::size_of::<&i32>()); // 8 (64bit)
println!("String: {} bytes", mem::size_of::<String>()); // 24
println!("Vec<i32>: {} bytes", mem::size_of::<Vec<i32>>()); // 24
}
7.7.2 動的サイズ型(DST)
コンパイル時にサイズが不明な型:
// str(文字列スライス)はDST
let s: &str = "hello"; // ✅ OK(参照は固定サイズ)
// let s: str = "hello"; // ❌ エラー!サイズ不明
// [T](スライス)もDST
let arr: &[i32] = &[1, 2, 3]; // ✅ OK
// let arr: [i32] = [1, 2, 3]; // ❌ エラー!
Sized トレイト:
fn generic<T: Sized>(t: T) {
// Tはコンパイル時にサイズが既知
}
// DSTを許可
fn generic_dyn<T: ?Sized>(t: &T) {
// Tはコンパイル時にサイズ不明でもOK
}
---
7.8 型の特性
7.8.1 Copy vs Clone
// Copyトレイト:スタック上のビット単位コピー
let x = 5;
let y = x; // xはコピーされる
println!("{}, {}", x, y); // ✅ xもyも使える
// Cloneトレイト:明示的な複製
let s1 = String::from("hello");
let s2 = s1.clone(); // s1をクローン
println!("{}, {}", s1, s2); // ✅ 両方使える
Copyを実装できる型:
┌─────────────────────────────────────────────────────────┐
│ Copyトレイトの要件 │
├─────────────────────────────────────────────────────────┤
│ │
│ ✅ 全てのフィールドがCopyを実装 │
│ ✅ スタック上に完全に格納 │
│ ❌ Dropトレイトを実装していない │
│ │
│ 例: │
│ ✅ i32, f64, bool, char │
│ ✅ タプル (i32, i32) │
│ ✅ 配列 [i32; 10] │
│ ❌ String, Vec<T>, Box<T> │
│ │
└─────────────────────────────────────────────────────────┘
7.8.2 Drop トレイト
値がスコープを抜ける時の処理:
struct CustomDrop {
data: String,
}
impl Drop for CustomDrop {
fn drop(&mut self) {
println!("Dropping: {}", self.data);
}
}
fn main() {
let x = CustomDrop {
data: String::from("my data"),
};
println!("Created");
} // ここでdropが呼ばれる
---
7.9 実践例
例1:型変換ユーティリティ
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse::<i32>()
}
fn bytes_to_string(bytes: Vec<u8>) -> Result<String, std::string::FromUtf8Error> {
String::from_utf8(bytes)
}
fn celsius_to_fahrenheit(celsius: f64) -> f64 {
celsius * 1.8 + 32.0
}
fn main() {
match parse_number("42") {
Ok(n) => println!("数値: {}", n),
Err(e) => println!("エラー: {}", e),
}
let temp_c = 25.0;
let temp_f = celsius_to_fahrenheit(temp_c);
println!("{}°C = {}°F", temp_c, temp_f);
}
例2:型安全なID
struct UserId(u64);
struct ProductId(u64);
impl UserId {
fn new(id: u64) -> Self {
UserId(id)
}
fn value(&self) -> u64 {
self.0
}
}
impl ProductId {
fn new(id: u64) -> Self {
ProductId(id)
}
fn value(&self) -> u64 {
self.0
}
}
fn get_user(id: UserId) {
println!("ユーザーID: {}", id.value());
}
fn get_product(id: ProductId) {
println!("商品ID: {}", id.value());
}
fn main() {
let user_id = UserId::new(1);
let product_id = ProductId::new(100);
get_user(user_id);
get_product(product_id);
// get_user(product_id); // ❌ コンパイルエラー!型が違う
}
---
7.10 まとめ
学んだこと
- 型システムの基本
- プリミティブ型
- 複合型
- 型変換
- 型の特性
次のステップ
次の章では、制御構造を学びます:
- 条件分岐(if、match)
- ループ(loop、while、for)
- パターンマッチング
- The Rust Programming Language - Chapter 3.2
- Rust By Example - Types
- Rust Reference - Types
---