はじめに
Dart 3.0で Records型が導入されました。 Dart 3.0は、Flutter 3.10で採用されているので、Flutter 3.10から Records型が使えます。
本記事では、このRecords型について解説します。
対象読者
- FlutterやDartの経験がある人で、Dart 3.0の新機能に興味ある人
- FlutterやDartの基本については説明しません。
Records型を使ってみる
Records型を利用することにより、複雑な型を持つデータを簡単に作成できるようになります。
まずは、簡単な、名前(String
)と年齢(int
)を返すような関数(userInfo
)を作る場合を例にあげて、Records型がどのように役に立つのかを見ていきます。
List
を使う
userInfo
の返り値として List
を使って、名前(String
)と年齢(int
)を返す場合のコードです。
// 名前と年齢を返す関数。
List<Object> userInfo() {
return ['John', 23];
}
void main() {
// Listで受け取る
final List<Object> info = userInfo();
final String name = info[0] as String; // type safeではない
final int age = info[1] as int;
print('name: $name, age: $age');
}
この場合、userInfo
のAPIとしての次のようなお約束があります。
- Listのサイズが 2
- 0番目の要素の型は
String
- 1番目の要素の型は
int
。
が、たとえ誤ったとしても、コンパイラエラーとならないので、問題の検出は実行時となってしまい、発見が遅れます。
クラスを使う
userInfo
の返り値として クラス(UserInfo
)を使って、名前(String
)と年齢(int
)を返す場合のコードです。
// クラスを作る
class UserInfo {
final String name;
final int age;
UserInfo(this.name, this.age);
}
UserInfo userInfo() {
return UserInfo('John', 23);
}
void main() {
final UserInfo info = userInfo();
final String name = info.name;
final int age = info.age;
print('name: $name, age: $age');
}
この場合、さきほどの Listを返すときの問題点はすべて解消されます。
しかしながら、クラス(UserInfo
)を作成するという手間がかかります。
Records型を使う
userInfo
の返り値として Records型を使う場合のコードです。
(String, int) userInfo() {
return ('John', 23);
}
void main() {
final (String, int) info = userInfo();
final String name = info.$1;
final int age = info.$2;
print('name: $name, age: $age');
}
クラスを使う と似ていますが、クラス作成をしていないだけ、短くなっています。
Records型とは
Record 文法
Records型は、無名な、イミュータブルな、アグリゲートな型です。
- 無名: クラスのように別途作成する必要がありません。その場で作成できます。
- イミュータブル: 作成したRecords型の値を変更することはできません。
- アグリゲート: 複数の型をまとめることができます。
Records型は、次のように表現します。
- フィールドは、コンマで区切る。
- 全フィールドを()で囲む。
- 2種類のフィールド
- ポジションフィールド
- 名前付きフィールド
var record = ('first', a: 2, b: true, 'last');
この例では、次のように2種類のフィールドが混在しています。
-
'first'
: 1番目のポジションフィールド。String
-
a: 2
: 名前a
を持つ、名前付きフィールド。int
-
b: true
: 名前b
を持つ、名前付きフィールド。bool
-
last
: 2番めのポジションフィールド。String
ポジションフィールドのRecords型
// Records型 recordの宣言。
(String, int) record;
// recordの初期化
record = ('A string', 123);
// 宣言と初期化をまとめてもよい
// (String, int) records = ('A string', 123);
// $1で 1番目の要素にアクセス
print(record.$1); // 'A string`
// $2で 2番目の要素にアクセス
print(record.$2); // 123
名前付きフィールドのRecords型
// Records型 recordの宣言。
({int a, bool b}) record;
// recordの初期化
record = (a: 123, b: true);
// 宣言と初期化をまとめてもよい
// ({int a, bool b}) records = (a: 123, b: true);
print(record.a); // 123
print(record.b); // true
名前付きフィールドのRecords型では、フィールド名が違う場合は、別の型となります。
({int a, int b}) recordAB = (a: 1, b: 2);
({int x, int y}) recordXY = (x: 3, y: 4);
// recordABとrecordXYは別の型とみなされるので、代入するとエラーなる。
// recordAB = recordXY;
ポジションフィールドの場合は、同じ型となります。
// ポジションフィールド場合、a, bは、ドキュメンテーションなので、型に影響しない
(int a, int b) recordAB = (1, 2);
(int x, int y) recordXY = (3, 4);
// 同じ型なので代入できる。
recordAB = recordXY; // OK.
Record フィールド
名前付きフィールドは、名前でアクセスします。
一方、ポジションフィールドは、$<position>
でアクセスします。
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'
Record と Pattern
パターンで各要素を取り出すことができる。
ポジションフィールドの場合
// ポジションフィールドのRecordsを返す関数
(String, int) userInfo() {
return ('John', 23);
}
void main() {
// 一旦変数(info)で受ける
final info = userInfo();
// で、各要素を取り出す。
print(info.$1);
print(info.$2);
// パターンを使って、各要素を直接取り出す
final (name, age) = userInfo();
print(name);
print(age);
}
名前付きフィールドの場合
// 名前付きフィールドのRecordsを返す関数
({String name, int age}) userInfo() {
return (name: 'John', age: 23);
}
void main() {
// 一旦変数(info)で受ける
final info = userInfo();
// で、各要素を取り出す。
print(info.name);
print(info.age);
// パターンを使って、各要素を直接取り出す
final (:name, :age) = userInfo();
print(name);
print(age);
// (:name, :age)は、 (name: name, age: age)の省略形
// 別変数で取り出したい場合は、次のように書く
//final (name :name1, age :age1) = userInfo();
//print(name1);
//print(age1);
}
あとがき
Records型の概要をざっくりと説明しました。
興味を持った人は、Codelabsをやってみましょう。
Dart 3.0で同じ導入されたPatternについては、最後に軽くふれた程度ですが、Recordsと合わせて使うと非常に強力な武器になるので、Patternについては、別途書こうと思います。