第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 まとめ

学んだこと

  • 型システムの基本
- 静的型付け - 型推論

  • プリミティブ型
- 整数、浮動小数点数、真偽値、文字

  • 複合型
- タプル、配列、スライス

  • 型変換
- asキャスト - From/Into、TryFrom/TryInto

  • 型の特性
- Copy、Clone、Drop

次のステップ

次の章では、制御構造を学びます: