Dart 3.0で導入された最大の機能刷新が、レコード(Records) と パターンマッチング(Pattern Matching) です。
従来のオブジェクト指向言語では、複数の値をまとめて返したい場合に一時的なデータクラスを定義する必要がありました。
Dart 3では、型安全かつ軽量なレコード型や、データの形状と値に応じて直感的に分岐・分解できるパターンマッチング、そして式として評価できる switch 式が利用可能になりました。
この章では、現代のDart開発においてコードを劇的に簡潔にするこれらの最新機能をマスターします。
レコード(Records) は、複数の値を1つにまとめることができる匿名かつ不変(イミュータブル)な集約型(いわゆるタプル)です。
専用のクラスを定義することなく、関数から複数の値を型安全に返すことができます。
丸括弧 () で値を囲むことでレコードを作成します。各フィールドには $1, $2 でアクセスします。
(String, int) getUserInfo() {
return ('Alice', 25);
}
void main() {
final user = getUserInfo();
print('名前 (\$1): ${user.$1}');
print('年齢 (\$2): ${user.$2}');
}dart run positional_records.dart名前 ($1): Alice 年齢 ($2): 25
波括弧 {} を使うことで、フィールドに名前を付けることができます。
({String name, int age, bool isAdmin}) getDetailedUser() {
return (name: 'Bob', age: 30, isAdmin: true);
}
void main() {
final user = getDetailedUser();
print('名前: ${user.name}');
print('年齢: ${user.age}');
print('管理者: ${user.isAdmin}');
}dart run named_records.dart名前: Bob 年齢: 30 管理者: true
Dart 3のパターン構文を使用すると、レコード、List、Mapなどの複合データ構造を宣言的に分解(分解代入)して個別のローカル変数に抽出できます。
(String, int) getCoords() => ('Tokyo', 100);
({int x, int y}) getPoint() => (x: 10, y: 20);
void main() {
// 位置レコードの分解
final (city, population) = getCoords();
print('都市: $city, 人口: $population 万人');
// 名前付きレコードの分解
final (:x, :y) = getPoint();
print('座標: x=$x, y=$y');
}dart run destructure_records.dart都市: Tokyo, 人口: 100 万人 座標: x=10, y=20
リストの要素数や中身に一致するパターンを使って値を抽出できます。不要な要素は _(ワイルドカード)で無視できます。
void main() {
// List の分解
final numbers = [1, 2, 3, 4];
final [first, second, _, fourth] = numbers;
print('1番目: $first, 2番目: $second, 4番目: $fourth');
// Map の分解
final json = {'id': 'user_123', 'status': 'active'};
final {'id': String userId, 'status': String userStatus} = json;
print('ユーザーID: $userId, ステータス: $userStatus');
}dart run destructure_collections.dart1番目: 1, 2番目: 2, 4番目: 4 ユーザーID: user_123, ステータス: active
switch 式従来の switch 文に加え、Dart 3では評価結果の値を返す switch 式(Expression) が導入されました。
すべてのケースが網羅されているかコンパイラが厳密に検証する 網羅性チェック が働きます。
String describeHttpCode(int statusCode) {
return switch (statusCode) {
200 => '成功 (OK)',
400 => '不正なリクエスト (Bad Request)',
404 => '未検出 (Not Found)',
500 => 'サーバーエラー (Internal Server Error)',
_ => '不明なステータスコード ($statusCode)',
};
}
void main() {
print(describeHttpCode(200));
print(describeHttpCode(404));
print(describeHttpCode(418));
}dart run switch_expression.dart成功 (OK) 未検出 (Not Found) 不明なステータスコード (418)
switch 式の中でデータの型や形状を検証しながら変数を取り出すことができます。
String formatData(dynamic data) {
return switch (data) {
// 整数で 0 の場合
0 => 'ゼロ',
// 正の整数の場合 (関係演算子パターン)
int n && > 0 => '正の整数: $n',
// 2要素のリストの場合
[var a, var b] => '2要素リスト: ($a, $b)',
// 特定のキーを持つマップの場合
{'name': String name, 'age': int age} => '名前: $name, 年齢: $age',
_ => 'その他のデータ',
};
}
void main() {
print(formatData(10));
print(formatData(['apple', 'banana']));
print(formatData({'name': 'Alice', 'age': 20}));
print(formatData(false));
}dart run pattern_matching_complex.dart正の整数: 10 2要素リスト: (apple, banana) 名前: Alice, 年齢: 20 その他のデータ
when)を使った条件分岐パターンマッチングに when 節(Guard句) を組み合わせることで、パターンの形状が一致した上でさらに任意のブール条件を課すことができます。
条件が false であれば次のマッチング候補へとフォールスルー(移動)します。
String evaluateScore((String, int) student) {
return switch (student) {
(var name, var score) when score == 100 => '$nameさん: 満点!素晴らしい!',
(var name, var score) when score >= 80 => '$nameさん: 優秀です (点数: $score)',
(var name, var score) when score >= 60 => '$nameさん: 合格 (点数: $score)',
(var name, var score) => '$nameさん: 要再試験 (点数: $score)',
};
}
void main() {
print(evaluateScore(('Alice', 100)));
print(evaluateScore(('Bob', 85)));
print(evaluateScore(('Charlie', 62)));
print(evaluateScore(('Dave', 45)));
}dart run guards_example.dartAliceさん: 満点!素晴らしい! Bobさん: 優秀です (点数: 85) Charlieさん: 合格 (点数: 62) Daveさん: 要再試験 (点数: 45)
この章では、Dart 3で導入された レコード と パターンマッチング について学びました。
switch 式: 式として評価され、コンパイラによる網羅性チェックが働く安全でコンパクトな条件分岐。when): パターンの形状一致に加えて追加のブール条件をスマートに記述できる。次の 第6章 では、オブジェクト指向プログラミングの中核である クラスと各種コンストラクタ について学びます。
2点間のマンハッタン距離と中点の座標を同時に計算して返す関数を作成してください。
({int x, int y}) を引数として2つ受け取る関数 analyzePoints を定義する。distance($|x_1 - x_2| + |y_1 - y_2|$)と中点 midpoint(レコード (double x, double y))を含む名前付きレコード ({int distance, (double, double) midpoint}) を返す。main() で p1 = (x: 0, y: 0) と p2 = (x: 4, y: 6) を渡して呼び出し、分解代入で結果を受け取って出力する。// ここに関数を定義してください
void main() {
// ここで動作確認を行ってください
}dart run practice5_1.dartCLIツールに入力された文字列コマンド(引数リスト List<String>)を解析する関数 handleCommand を作成してください。
handleCommand(List<String> command) を定義し、switch 式を使って以下のパターンを処理する。
['help'] => 'ヘルプを表示します'['view', var id] => 'ID: $id の詳細を表示します'['create', var name, var countStr] when int.tryParse(countStr) != null => '新規作成: $name ($countStr 個)'_ => '無効なコマンドです'main() でそれぞれのコマンドパターンを渡し、正しく分岐されることを確認する。// ここに関数を定義してください
void main() {
// ここで動作確認を行ってください
}dart run practice5_2.dart