# 機能設計書 56-Dataクラス

## 概要

本ドキュメントは、mrubyにおけるDataクラスの設計を記述する。DataクラスはRuby 3.2で導入されたイミュータブルな値オブジェクトクラスを定義する機能を提供する。Structに類似するが、生成されたインスタンスは凍結（frozen）される。

### 本機能の処理概要

Data.defineで名前付きフィールドを指定してイミュータブルなデータクラスを動的に生成する。Structとは異なりsetterは定義されず、インスタンスは生成後に凍結されて変更不可となる。

**業務上の目的・背景**：値オブジェクト（Value Object）パターンを実装するための基盤を提供する。設定データ、座標、色情報など、一度生成したら変更すべきでないデータ構造を型安全に表現できる。Structとは異なり不変性が保証されるため、マルチスレッド環境やハッシュキーとしても安全に使用できる。

**機能の利用シーン**：イミュータブルな設定値の保持、値オブジェクトの定義、安全なハッシュキーの作成、APIレスポンスの構造化など。

**主要な処理内容**：
1. Data.define(:field1, :field2, ...)で新しいDataサブクラスを生成
2. 各フィールドに対応するgetterメソッド（setterなし）を自動定義
3. 生成されたインスタンスは自動的にfreezeされる
4. キーワード引数または位置引数でインスタンスを生成
5. ==, eql?, to_h, to_s, inspect等のメソッドを提供

**関連システム・外部連携**：内部的にはStructと同じMRB_TT_STRUCT型を使用するが、setterを持たず、インスタンスが自動凍結される点が異なる。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | ライブラリ機能であり、画面との直接的関連なし |

## 機能種別

イミュータブルデータ構造体生成

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| members（define） | Symbol可変長 | Yes | フィールド名のリスト | シンボルに変換可能、重複なし |
| args（new） | 位置引数 or キーワード引数 | Yes | フィールドの値 | メンバー数と一致 |
| block（define） | Block | No | クラス定義ブロック | - |

### 入力データソース

Rubyプログラムからの直接呼び出し。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 生成されたクラス | Class | Dataのサブクラス |
| インスタンス | Data | 凍結されたDataインスタンス |

### 出力先

呼び出し元のRubyプログラムへの戻り値。

## 処理フロー

### 処理シーケンス

```
1. Data.define(:field1, :field2, ...) の呼び出し
   └─ メンバー名の重複チェック
2. make_data_class() で新しいクラスを生成
   └─ mrb_class_newでDataのサブクラスを作成
   └─ MRB_TT_STRUCTをインスタンスタイプに設定
   └─ defineメソッドをundefし、newメソッドを再定義
3. getterのみを自動定義（setterは定義しない）
   └─ 4メンバーまで: 直接関数ポインタ（data_ref_0〜3）で最適化
   └─ 5メンバー以降: mrb_proc_new_cfunc_with_envで生成
4. DataClass.new(args) でインスタンス生成
   └─ 位置引数またはキーワード引数を受け付ける
   └─ 値を配列に格納
   └─ mrb_obj_freeze()でインスタンスを凍結
```

### フローチャート

```mermaid
flowchart TD
    A[Data.define :f1, :f2, ...] --> B[メンバー重複チェック]
    B --> C[make_data_class]
    C --> D[mrb_class_new]
    D --> E[getter自動定義のみ]
    E --> F{メンバー数 <= 4?}
    F -->|Yes| G[直接関数ポインタ data_ref_N]
    F -->|No| H[mrb_proc_new_cfunc_with_env]
    G --> I[__members__にメンバー保存]
    H --> I
    I --> J[DataClass.new]
    J --> K{キーワード引数?}
    K -->|Yes| L[kwargsで値取得]
    K -->|No| M[位置引数で値取得]
    L --> N[配列に格納]
    M --> N
    N --> O[mrb_obj_freeze でインスタンス凍結]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-56-01 | イミュータブル | Dataインスタンスは生成後に自動的にfreezeされ、変更不可 | インスタンス生成時 |
| BR-56-02 | setterなし | Structとは異なり、setterメソッドは定義されない | 常時 |
| BR-56-03 | メンバー重複禁止 | 同じ名前のフィールドを重複定義できない | Data.define呼び出し時 |
| BR-56-04 | キーワード引数対応 | new時にキーワード引数と位置引数の両方をサポート | インスタンス生成時 |
| BR-56-05 | 値の等価性 | ==はクラスが同一かつ全フィールドの値が等しい場合にtrue | ==呼び出し時 |

### 計算ロジック

特になし。

## データベース操作仕様

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ArgumentError | メンバーが重複 | 一意なメンバー名を使用 |
| - | ArgumentError | 引数の数がメンバー数と不一致 | 正しい数の引数を提供 |
| - | ArgumentError | 未定義のメンバー名をキーワード引数で指定 | 定義されたメンバー名を使用 |
| - | TypeError | 破損したDataオブジェクトの操作 | 正常なDataオブジェクトを使用 |

### リトライ仕様

リトライ不要。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- 4メンバーまでは直接関数ポインタ（data_ref_0〜3）を使用し、環境変数参照のオーバーヘッドを回避
- DATA_DIRECT_REF_MAX = 4 で最適化閾値を定義

## セキュリティ考慮事項

- インスタンスが自動凍結されるため、不意の変更からデータを保護できる

## 備考

- Dataクラスは内部的にMRB_TT_STRUCT型を使用し、Structと同じ配列ベースの実装
- MRB_UNDEF_ALLOCATORでデフォルトアロケータを無効化し、Data.newは使用不可（Data.defineのみ）
- initialize_copyもfreezeを呼び出し、コピーもイミュータブルに保つ

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | data.c | `mrbgems/mruby-data/src/data.c` | RDATA_LEN/RDATA_PTRマクロ（RARRAY_LEN/PTRのエイリアス）（18-19行目） |
| 1-2 | data.c | `mrbgems/mruby-data/src/data.c` | data_p()マクロ: MRB_TT_STRUCT型チェック（21行目） |
| 1-3 | data.c | `mrbgems/mruby-data/src/data.c` | DATA_DIRECT_REF_MAX = 4: 直接参照の最適化閾値（153行目） |

**読解のコツ**: DataクラスはStructと同じMRB_TT_STRUCT型を使用する。内部実装はStructに類似しているが、setterが定義されない点とインスタンスが自動凍結される点が重要な違いである。

#### Step 2: クラス生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | data.c | `mrbgems/mruby-data/src/data.c` | mrb_data_s_def(): Data.defineの実装（286-315行目） |
| 2-2 | data.c | `mrbgems/mruby-data/src/data.c` | make_data_class(): サブクラス生成（251-266行目） |
| 2-3 | data.c | `mrbgems/mruby-data/src/data.c` | make_data_define_accessors(): getterのみ定義（162-184行目） |

**主要処理フロー**:
- **295-309行目**: メンバーの重複チェック
- **257行目**: mrb_undef_class_method(c, "define") - サブクラスでdefineを使用不可に
- **258行目**: newメソッドをmrb_data_newとして再定義
- **172-173行目**: 4メンバーまでは直接関数ポインタ（aref配列）で最適化

#### Step 3: インスタンス生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | data.c | `mrbgems/mruby-data/src/data.c` | mrb_data_new(): キーワード/位置引数対応のnew（201-248行目） |
| 3-2 | data.c | `mrbgems/mruby-data/src/data.c` | mrb_data_initialize(): ハッシュからの初期化（327-349行目） |

**主要処理フロー**:
- **210行目**: nk > 0でキーワード引数を判定
- **229行目**: MRB_OBJ_ALLOCでMRB_TT_STRUCTインスタンスを割り当て
- **246行目**: mrb_obj_freeze(mrb, data) で自動凍結

#### Step 4: 比較・変換メソッドを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | data.c | `mrbgems/mruby-data/src/data.c` | mrb_data_equal(): ==の実装（395-421行目） |
| 4-2 | data.c | `mrbgems/mruby-data/src/data.c` | mrb_data_to_h(): to_hの実装（462-475行目） |
| 4-3 | data.c | `mrbgems/mruby-data/src/data.c` | mrb_data_to_s(): to_s/inspectの実装（484-510行目） |

### プログラム呼び出し階層図

```
Data.define(:field1, :field2, ...)  [mrb_data_s_def]
    |
    +-- メンバー重複チェック
    +-- make_data_class()
    |    +-- mrb_class_new() [サブクラス生成]
    |    +-- MRB_SET_INSTANCE_TT(MRB_TT_STRUCT)
    |    +-- make_data_define_accessors()
    |         +-- data_ref_0/1/2/3 [最適化getter, <= 4メンバー]
    |         +-- mrb_data_ref [汎用getter, > 4メンバー]
    |
    +-- DataClass.new(args)  [mrb_data_new]
         +-- キーワード引数 / 位置引数パース
         +-- MRB_OBJ_ALLOC(MRB_TT_STRUCT)
         +-- mrb_ary_set() [値格納]
         +-- mrb_obj_freeze() [自動凍結]
```

### データフロー図

```
[入力]                        [処理]                       [出力]

:field1, :field2, ...  ──> Data.define()  ─────────────> Class (Dataサブクラス)
                            |
                            +-- getterのみ定義
                            +-- __members__設定

val1, val2 (or kw args) -> DataClass.new() ───────────> Data instance (frozen)
                            |
                            +-- 配列に値格納
                            +-- freeze
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| data.c | `mrbgems/mruby-data/src/data.c` | ソース | Dataクラスの全C実装 |
| data.rb | `mrbgems/mruby-data/test/data.rb` | テスト | Dataクラスの動作テスト |
| mrbgem.rake | `mrbgems/mruby-data/mrbgem.rake` | 設定 | gemメタデータ定義 |
