【入門編】Dartのレコード型で実現する「構造的型付け」に近い柔軟なデータ受け渡し – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!Dartの奥深い世界へようこそ。
Flutterでのアプリ開発やDartでのバックエンド開発、楽しんでいますか?

今回は、Dart 3で導入された「レコード型(Records)」をテーマに、クラスをわざわざ作らなくても型安全にデータをやり取りするスマートな方法を解説していきますね。

他のオブジェクト指向言語(JavaやC#など)からDartに入った方だと、「ちょっとした複数のデータをまとめたいだけなのに、わざわざ `class` を定義しなきゃいけないの……?」と面倒に感じたことがあるのではないでしょうか。

ここをクリアすれば、あなたのDartコードは一気に軽やかで読みやすくなりますよ。さあ、一緒にマスターしていきましょう!

—

1. なぜ「ちょっとしたデータ」のためにクラスを作るのは面倒なのか?

例えば、画面の座標を表す `x` と `y` や、ユーザーの「名前」と「年齢」をまとめて関数から返したいとします。従来のDartだと、こんな風にクラスを書く必要がありました。

// 従来のクラス定義
class UserInfo {
final String name;
final int age;

UserInfo(this.name, this.age);
}

UserInfo getUser() {
return UserInfo(‘Alice’, 30);
}

たった2つの値(名前と年齢)を返すためだけに、コンストラクタやプロパティのボイラープレート(お決まりのコード)を書くのは、ちょっと大げさですよね。かといって、代わりに `Map` を使ってしまうと、キーのタイポ(打ち間違い)に気づけなかったり、値を取り出すときにキャストが必要になったりして、Dartの強みである「型安全性」が失われてしまいます。

そこで登場するのが、レコード型です!

—

2. レコード型とは何か?(構造的型付けの心地よさ)

レコード型を使うと、名前のない「その場限りのデータ構造」を、型安全を完全に保ったままシンプルに記述できます。

百聞は一見にしかず。先ほどのコードをレコード型で書き換えてみましょう。

// レコード型を使って、名前と年齢をそのまま返す
(String, int) getUser() {
return (‘Alice’, 30); // 括弧で囲むだけで、即座にレコードが作れる
}

void main() {
// 受け取る側もスッキリ!
var user = getUser();

print(user.$1); // 出力: Alice ($1で1番目の要素にアクセス)
print(user.$2); // 出力: 30 ($2で2番目の要素にアクセス)
}

なんと、クラスの定義が一切不要になりました!
`(String, int)` という型がその場で生成され、コンパイラは「1番目は `String`、2番目は `int`」であることを完璧に把握しています。

📊 データの流れのイメージ

[関数: getUser]
│
├─> ‘Alice’ (String) ──> 構造化 ──> (String, int) 型のレコード
└─> 30 (int) ──┘ │
▼
【型安全を保ったまま受け渡し】
│
▼
[呼び出し元: main]
├─> user.$1 ──> ‘Alice’
└─> user.$2 ──> 30

このように、クラスという「名札(型名)」をわざわざ作らなくても、データの「構造(何が何個入っているか)」だけで型を定義する仕組みを構造的型付け(に近しい恩恵)と呼びます。

—

3. さらに便利!名前付きフィールド(Named Fields)

「 `$1` とか `$2` だと、何が何番目か忘れそう……」と思いましたか?
ご安心ください。レコードには名前を付けることもできます。

// フィールドに名前をつけたレコードを返す
({String name, int age}) getUserProfile() {
return (name: ‘Bob’, age: 25);
}

void main() {
var user = getUserProfile();

// 番号ではなく、名前で安全にアクセスできる!
print(user.name); // 出力: Bob
print(user.age); // 出力: 25
}

これなら、位置を気にする必要がありませんし、コードの意図がひと目で伝わりますよね。

—

4. 陥りやすい文法エラーと注意点

非常に便利なレコードですが、初学者のうちは以下のようなミスでコンパイルエラーになりがちです。気をつけておきましょう!

⚠️ 注意点1: フィールド名を外から変えることはできない(イミュータブル)

レコードの要素はすべて `final` 扱いです。一度生成したレコードの値を書き換えることはできません。

var user = (name: ‘Alice’, age: 30);
// user.age = 31; ❌ コンパイルエラー!値を直接書き換えることはできません

※もし値を更新したい場合は、新しいレコードを代入し直すことになります。

⚠️ 注意点2: 1要素だけのレコードを書くときの罠

「要素が1つのレコード」を作りたいとき、うっかり `(String)` と書くと、それはただの「優先順位を変えるためのカッコ(括弧)」とみなされてしまいます。
1要素のレコードを作るには、末尾にカンマ( `,` )が必須です。

// ❌ 間違い:これは単なるString型の変数(括弧の意味になる)
(String) notARecord = (‘Hello’);

li// ⭕ 正解:末尾にカンマをつけることで、1要素のレコードになる
(String,) actualRecord = (‘Hello’,);

print(actualRecord.$1); // 出力: Hello

この「末尾のカンマ」は、Dartのレコード構文で一番ハマりやすいポイントなので、ぜひ覚えておいてくださいね。

—

5. 実戦でどう使う? パターンマッチングとの組み合わせ

レコードの真骨頂は、Dart 3のパターンマッチング(Destructuring)と組み合わせたときに発揮されます。関数から受け取った複数の値を、1行で分解して変数に割り当てることができます。

(String, int, bool) fetchCurrentStatus() {
// 名前、レベル、ログイン中かどうかを返す
return (‘Charlie’, 42, true);
}

void main() {
// パターンマッチングで一気に変数へ分解!
var (name, level, isLoggedIn) = fetchCurrentStatus();

print(‘$nameさんはレベル$levelで、ログイン状況は $isLoggedIn です。’);
// 出力: Charlieさんはレベル42で、ログイン状況は true です。
}

わざわざ `user.$1` のようにアクセスしなくても、意味のある変数名にスッと分解できるため、コードの可読性が劇的に向上します。

—

まとめ

今回はDartのレコード型について解説しました。

  • クラスを作るほどでもない一時的なデータのまとめ役に最適
  • 位置ベース (`$1`, `$2`) と名前ベース (`.name`, `.age`) の両方が使える
  • 1要素のときは末尾のカンマ `,` を忘れない
  • パターンマッチングと組み合わせると最強にスッキリ書ける

「ちょっとしたデータを安全にまとめたい」という場面に出会ったら、ぜひ今回のレコード型を思い出して使ってみてください。あなたのDartコードが、より洗練されたものになるはずです。

ここをクリアできれば、Dartのモダンなデータハンドリングはもうバッチリマスターですよ!
それでは、次のFlutter/Dart開発も楽しんでいきましょう!

タイトルとURLをコピーしました