Records에 대한 다음 설명은, Dart 공식 사이트의 내용(아래의 출처 참조)을 기반으로, 독자의 이해를 돕기 위한 추가적인 글을 포함하거나, 출처의 글을 수정하는 방식으로 작성하였습니다.
Dart 3.0 부터 지원하는 Records는 anonymous, immutable 한 특성을 갖는 새로운 형태의 aggregate type 입니다. 따라서 하나의 객체에 복수의 객체를 저장할 수 있습니다. 하지만 다른 복수의 값을 갖는 collection types(예를 들어 List, Map 등)들과 다르게 고정된 크기와 타입을 갖습니다.
Records 타입의 값은 변수에 저장할 수 있습니다. 함수의 입력 값으로 전달하거나, 리턴 값으로 전달 받을 수 있습니다. 그리고 List, Map, Set과 같은 collection type에 저장할 수도 있습니다.
Records는 쉼표로 구분한 값들이 소괄호 안에 나열된 형태로 표현합니다. 이 경우 named 방식과 positional 방식을 혼용해서 사용할 수 있습니다. 즉 아래의 예제에서 ‘first’와 ‘last’는 positional 방식으로, 추후 순서에 의해서 값에 접근할 수 있습니다. 2와 true 값은 각각 a와 b의 이름을 부여한 방식입니다.
var record = ('first', a: 2, b: true, 'last');
Records에 포함된 항목(field)은 자동으로 생성된 getter에 의해서 접근 가능합니다. 그리고 Records는 immutable하기에 setter는 없습니다. Records에 포함된 항목이 named 방식인 경우는 Record의 이름에 점(.) 연산자를 사용하여 접근합니다. Positional 항목은 $<position> 문법을 사용합니다. 다음은 Record인 record의 항목을 접근하는 예제입니다.
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'
예제에서 이름이 a와 b인 항목을 record.a와 record.b로 사용하는 것은 명확합니다. 그런데 이름이 없기에 위치로 접근해야 하는 ‘first’와 ‘last’에 대해서는 설명이 필요합니다. 간단하게 이야기 하면, Records가 포함하고 있는 항목 중 named 항목이 아닌 첫번째 Positional 항목에 대해서 $1의 임시 이름을 부여하며, 그 다음의 Positional 항목에 대해서 1의 값이 증가한 $2를 부여합니다. 그리고 (‘first’, a: 2, b: true, ‘last’)은 immutable 하고 setter가 없기에, record.$1 = ‘third’ 혹은 record.a = 3의 구문은 동작하지 않습니다.
Records 타입은 함수의 입력 파라메터로 전달할 수 있고, 리턴 값으로도 전달 받을 수 있습니다. 다음의 예제는 (int, int) 타입의 Record를 swap() 함수의 입력 파라메터로 전달한 후, 다시 (int, int) 타입의 결과로 리턴 받는 경우를 보여줍니다.
(int, int) swap((int, int) record) {
var (a, b) = record;
return (b, a);
}
Records 타입으로 변수를 생성하는 것도 가능합니다. 다음은 Positional한 항목인 String과 int를 갖는 Record 타입의 변수 record를 생성하고, 값을 저장하는 예제입니다.
// Record type annotation in a variable declaration:
(String, int) record;
// Initialize it with a record expression:
record = ('A string', 123);
Dart 언어의 입력 파라메터를 전달받는 방법에서 다룬 named 파라메터를 활용하여, Records 타입의 변수 생성시 named 파라메터를 갖도록 하는 것도 가능합니다. 이 경우 함수의 입력 파라메터를 named 방식으로 만들기 위하여 사용한 중괄호({}) 문법을 동일하게 사용합니다. 다음은 Records 타입의 변수 record를 생성하면서, 포함되는 첫번째 항목의 이름은 a로 하고 두번째 항목의 이름은 b로 하는 것을 보여줍니다.
// Record type annotation in a variable declaration:
({int a, bool b}) record;
// Initialize it with a record expression:
record = (a: 123, b: true);
Named 방식을 사용하기에, 당연히 record = (b: true, a: 123) 표현도 동일한 결과를 만듭니다.
주의해야할 사항은, 항목의 이름인 a와 b도 record의 타입 안에 고정적인 형태로 설정된다는 점 입니다. 이는 다음의 예제에서 알 수 있습니다.
({int a, int b}) recordAB = (a: 1, b: 2);
({int x, int y}) recordXY = (x: 3, y: 4);
// Compile error! These records don't have the same type.
// recordAB = recordXY;
recordAB와 recordXY는 Records 타입이며, 동일하게 정수 두 개를 항목으로 갖습니다. 하지만, named 파라메터 방식을 사용하기에, 두 항목의 타입 뿐 아니라 항목에 주어진 이름도 동일해야 비교가 가능하며, 이름이 다른 항목을 가진 Records 간의 비교는 불가능합니다. Named 방식이 아닌 경우는 당연히 항목의 타입만 동일하면 비교가 가능하며, 이에 대한 예제는 다음과 같습니다.
(int a, int b) recordAB = (1, 2);
(int x, int y) recordXY = (3, 4);
recordAB = recordXY; // OK.
Dart 언어에서 Records는 shape에 의해서 처리된다고 정의합니다. Shape는 필드의 집한, 필드의 타입, 그리고 필드의 이름 등을 의미합니다. 다음의 예제에서는 pair에 주어지는 (42, ‘a’)에 대해서, 각각 num 타입과 Object 타입으로 매핑되어 처리되는 것을 보여줍니다.
(num, Object) pair = (42, 'a');
var first = pair.$1; // Static type `num`, runtime type `int`.
var second = pair.$2; // Static type `Object`, runtime type `String`.
Records의 가장 손쉬운 사용 방법은 복수의 값을 리턴하는 함수입니다. 다음의 예제는 userInfo() 함수가 데이터베이스 레코드의 이름 필드와 나이 필드의 값을 함수의 결과로 리턴하는 예제를 보여줍니다.
// Returns multiple values in a record:
(String, int) userInfo(Map<String, dynamic> json) {
return (json['name'] as String, json['age'] as int);
}
final json = <String, dynamic>{
'name': 'Dash',
'age': 10,
'color': 'blue',
};
// Destructures using a record pattern:
var (name, age) = userInfo(json);
/* Equivalent to:
var info = userInfo(json);
var name = info.$1;
var age = info.$2;
*/
리턴 된 결과는 받는 부분에서 볼 수 있듯이, 두 개의 결과가 name과 age에 각각 매핑되는 것을 볼 수 있습니다. 물론 두 개 이상의 값을 리턴하는 문장을 List, Map, Set 등의 collection type을 통해서 구현할 수 있습니다. 하지만, 이들은 두 개 보다 더 많은 대량의 데이터를 다루는 타입으로서, 주어진 두 개의 결과를 리턴하는 방식에 최선의 기술은 아닙니다. 따라서, Dart 3.0은 발전된 방법의 Records를 제공합니다.
[출처] https://dart.dev/language/records
[참조] https://api.flutter.dev/flutter/dart-core/Record-class.html