Dart (3.13) Module 6 – Records and pattern matching
Records in Dart: several values without a class
Bundle values into a Dart record, read its fields, see why equal records find the same map entry, and decide when a class or a map fits better.
What you will learn
- Create records with positional and named fields and read each field through its getter
- Explain which parts of a record type make up its shape, and which field names are only documentation
- Predict when two records are equal, and use records as map keys and set elements
- Write a function that returns several values in one record and read them at the call site
- Choose between a record, a class and a map for a piece of structured data
Before you start
What you’ll learn · Before you start
What you will learn
- Create records with positional and named fields and read each field through its getter
- Explain which parts of a record type make up its shape, and which field names are only documentation
- Predict when two records are equal, and use records as map keys and set elements
- Write a function that returns several values in one record and read them at the call site
- Choose between a record, a class and a map for a piece of structured data
Before you start
On this page
A function that finds the coldest and the hottest of five readings has two answers, 27 and 35, but a return statement
hands back one value. Before Dart 3 that meant a List that forgets which number is which, a Map whose keys nobody
checks, or a class written only to carry two ints. A record carries both in one value, with a type the compiler checks
and an == that compares contents.
You will build records with positional and named fields and read them through their getters, see which names belong to a record’s type, measure when two records are equal and use them as keys of a seat map, return two values from one function, and decide when a class or a map fits better than a record.
A record holds a fixed set of fields, read through getters
ticket.dart builds one value for a train ticket: the train number and the two stations, plus the coach and the seat by name:
void main() {
// Three positional fields and two named ones, written in any order.
final ticket = ('12127', coach: 'B2', 'Pune', seat: 41, 'Mumbai');
print(ticket);
print('train ${ticket.$1}, from ${ticket.$2} to ${ticket.$3}');
print('coach ${ticket.coach}, seat ${ticket.seat}');
} Output
(12127, Pune, Mumbai, coach: B2, seat: 41) train 12127, from Pune to Mumbai coach B2, seat 41
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run ticket.dart
All five values went into one object, written as a comma-separated list in parentheses. That object is a record: An anonymous, immutable value that bundles a fixed set of positional and named fields, each with its own type. Records need no declaration and compare equal when their shape and fields are equal.; the records page calls records an “anonymous, immutable, aggregate type”, since a record needs no declaration, cannot change, and bundles several values. The values written on their own are positional field: A field of a record written without a name and read by its position, as $1, $2 and so on, counting only the positional fields from the left., and the ones written as a name, a colon and a value are named field: A field of a record written as name: value and read through a getter of the same name. Its name is part of the record's type, and the order of named fields is not.. The two kinds may be mixed in any order. Step through how each field is read:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- Five fields in one record. The record expression lists five fields between parentheses. Three are positional, written as plain values, and two are named, written as a name, a colon and a value. Named and positional fields may be mixed in any order.
- Positional fields are numbered. Only positional fields get a number, counted from the left from 1: '12127' is $1, 'Pune' is $2 and 'Mumbai' is $3. The named fields between them take no number, which is why 'Pune' is $2 and not $3.
- Named fields keep their names. Each named field is read by a getter with its own name, ticket.coach and ticket.seat. The program printed coach B2, seat 41.
- Getters only, no setters. Every field is read through a getter, and there are no setters, so no field can be given a new value. print showed the positional fields first and then the named ones.
Positional fields are read as $1, $2 and $3, numbered among the positional fields only, so ‘Pune’ is $2 even
though coach stands between it and the train number. Named fields are read by their names. The printed form put the
positional fields first; the specification leaves the order of named fields in that text open and lets optimised
builds print records in other ways, so use it for debugging, never as output a user reads.
Every field has a getter and none has a setter. no_setter.dart tries to move a passenger to row C:
void main() {
final seat = (row: 'B', number: 12);
seat.row = 'C';
print(seat);
} Output (exit status 254)
Printed as an error (standard error)
no_setter.dart:3:8: Error: The setter 'row' isn't defined for the type '({int number, String row})'.
Try correcting the name to the name of an existing setter, or defining a setter or field named 'row'.
seat.row = 'C';
^^^
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run no_setter.dart
The compiler stopped with “The setter ‘row’ isn’t defined” and the program never ran, with exit status 254. The type in
the message lists number before row: named fields are kept sorted by name, because the order you write them in is
not part of the type. To change a field, build a new record with the new value.
Parentheses alone do not make a record. one_field.dart wraps 42 twice, once with a comma:
void main() {
final a = (42);
final b = (42,);
print('(42) is ${a.runtimeType}, (42,) is ${b.runtimeType}');
print('b.\$1 = ${b.$1}');
final nothing = ();
print('() is ${nothing.runtimeType}');
} Output
(42) is int, (42,) is (int) b.$1 = 42 () is ()
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run one_field.dart
(42) is the number 42 in parentheses, an int. (42,) is a record with one positional field, whose type prints as
(int) and whose $1 is 42. The trailing comma is needed only for a single positional field: (city: 'Pune') is a
record without one, because the name already marks it, and () is the empty record.
A one-value record without its comma
final wrapped = (amount); gives wrapped the type of amount, so wrapped.$1 does not compile. Write
(amount,) when you really want a record with one positional field, or name the field: (amount: amount).
Checkpoint
Check: Positional and named fields and their getters
A record’s type is its shape, and named fields belong to it
Record types are written the way a function’s parameters are: (int, String) for two positional fields, and
({double lat, double lng}) for named fields, which go in braces after any positional ones. There is no declaration
for a record type. The records page says the set of fields, their types and their names make up the record’s
record shape: The number of positional fields of a record, the names of its named fields and the type of every field. The shape alone decides a record's type., and the shape alone decides the type. The next two programs test which names
count. The first gives Pune’s coordinates to a variable whose fields have other names:
void main() {
({double lat, double lng}) pune = (lat: 18.52, lng: 73.86);
({double latitude, double longitude}) sameCity = pune;
print(sameCity);
} Output (exit status 254)
Printed as an error (standard error)
named_shape.dart:3:52: Error: A value of type '({double lat, double lng})' can't be assigned to a variable of type '({double latitude, double longitude})'.
({double latitude, double longitude}) sameCity = pune;
^
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run named_shape.dart
The compiler refused it: the value typed ({double lat, double lng}) cannot be stored in a variable typed
({double latitude, double longitude}). Renaming a named field makes another type. Names on positional fields work
differently:
void main() {
(int paise, int quantity) line = (2500, 2);
// The names in a positional record type are only documentation:
(int quantity, int paise) relabelled = line;
print('relabelled.\$1 = ${relabelled.$1}');
print('same value: ${relabelled == line}');
} Output
relabelled.$1 = 2500 same value: true
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run field_names.dart
(int paise, int quantity) went into (int quantity, int paise) without a word, and relabelled.$1 still printed
2500, the price. The compiler matched the fields by position, and the names were a note for the reader. So when two
positional fields have the same type, nothing stops a caller from reading them the wrong way round; with named fields,
every read says which field it means. This table sums up which changes make a new record type:
| A change to a record type | Another type? |
|---|---|
| Rename a named field | yes |
| Rename a positional field in an annotation | no, those names are documentation |
| Write the named fields in another order | no |
| Add or remove a field | yes |
| Change the type of a field | yes |
Key idea
A record’s type is its shape: how many positional fields it has, the names of its named fields and the type of every field. Names written on positional fields are comments the compiler ignores.
A long record type is tiring to repeat, so give it a name with a typedef: A named alias for a type, most often a function type, such as typedef Validator = String? Function(String). It documents intent and keeps declarations readable; it creates no new class. More in “Closures, function types and tear-offs”. txn.dart names the shape of a payment and uses that name in a function’s signature:
typedef Txn = ({String id, int amountPaise, String vpa});
Txn refund(Txn t) => (id: '${t.id}-R', amountPaise: -t.amountPaise, vpa: t.vpa);
void main() {
final Txn paid = (id: 'T42', amountPaise: 25000, vpa: 'asha@okbank');
final back = refund(paid);
print(back);
// The alias and the written-out type are one and the same type.
({String id, int amountPaise, String vpa}) plain = back;
print('plain is Txn: ${plain is Txn}');
} Output
(amountPaise: -25000, id: T42-R, vpa: asha@okbank) plain is Txn: true
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run txn.dart
refund takes and returns a Txn, and plain is Txn printed true: the alias and the written-out record type are one
type, which is how the typedefs page describes every alias. The printout listed the fields as amountPaise, id and vpa,
sorted again rather than in the order written.
Payment handles and paise
The vpa field holds a UPI virtual payment address in the name@handle form. Keeping each amount as a whole number of paise in an int, never as rupees in a double, keeps every sum exact.
Checkpoint
Check: The shape of a record type
Records with equal fields are equal
equality.dart makes two records for one postcode, one from a literal and one from a sum, and then compares several pairs:
void main() {
final pin = 411000 + 1;
final a = ('Pune', 411001);
final b = ('Pune', pin);
print('a == b: ${a == b}');
print('equal hash codes: ${a.hashCode == b.hashCode}');
final x = (city: 'Pune', pin: 411001);
final y = (pin: 411001, city: 'Pune');
print('named fields in another order: ${x == y}');
final tags = ['veg', 'jain'];
print('one list in both: ${('Thali', tags) == ('Thali', tags)}');
final p = ('Thali', ['veg', 'jain']);
final q = ('Thali', ['veg', 'jain']);
print('two equal lists: ${p == q}');
final order = ('Thali', tags);
tags.add('no onion');
print(order);
} Output
a == b: true equal hash codes: true named fields in another order: true one list in both: true two equal lists: false (Thali, [veg, jain, no onion])
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run equality.dart
a == b printed true, and the two hash codes were equal too, although the records are separate objects made in
different ways. The records page gives the rule: two records are equal when they have the same shape and their
corresponding fields have equal values. That is structural equality: Equality decided by contents rather than by object: two records are equal when they have the same shape and every pair of corresponding fields is equal., and every
record gets it, together with a matching hashCode, without a line of code from you. Since the order of named fields
is not part of the shape, the third comparison printed true as well.
The fourth and fifth lines look alike but disagree. Step through what == did for p == q, the pair that printed
false:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- Same shape first. p and q each hold two positional fields, a String and a List of strings, so they have the same shape. That is the first thing == checks: a record with other fields, or other names on its named fields, is never equal to p.
- The first pair is equal. Then == compares each pair of fields with the == of the field's own value. String == compares the characters, and 'Thali' is equal to 'Thali'.
- Two lists, two objects. Each list literal built a list of its own. A List is equal only to itself, whatever its elements, so this pair is not equal although both lists hold veg and jain.
- One unequal pair decides. A single unequal pair makes the records unequal, and p == q printed false. When both records held the one tags list, their second fields were the same object, and the comparison printed true.
A record compares its fields with each field’s own ==, so the result is only as good as that operator. List’s
documentation says lists “are, by default, only equal to themselves”, which is
reference identity: Whether two references lead to one and the same object. identical() checks it and no class can change its answer; == checks it too unless the class overrides ==. More in “Operators, cascades and equality in Dart”: one shared list made the records equal, two lists with the
same elements did not. The last line shows a second consequence. After tags.add('no onion'), the record printed the
new element: a record cannot point a field at another object, but the object a field holds can still change.
Treating a record as a frozen copy of a list
final order = ('Thali', tags); does not copy tags, so every later tags.add(...) shows up in order. When the
record must keep the values it had, build it from a copy of the list instead of the list itself.
Checkpoint
Check: When two records are equal
Equal records find the same map entry
A cinema keeps its bookings in a map from seat to name, and the key of each entry is a record with a row and a seat number. seat_map.dart books two seats, looks them up with records built later, and collects picked seats in a set:
void main() {
final booked = <({String row, int seat}), String>{};
booked[(row: 'B', seat: 12)] = 'Asha';
booked[(row: 'B', seat: 13)] = 'Ravi';
// A new record with equal fields finds the same entry.
final wanted = (row: 'B', seat: 6 * 2);
print('B12 is booked by ${booked[wanted]}');
print('B13 taken: ${booked.containsKey((seat: 13, row: 'B'))}');
print('B14 taken: ${booked.containsKey((row: 'B', seat: 14))}');
final picked = {
(row: 'C', seat: 1),
(row: 'C', seat: 1),
(row: 'C', seat: 2),
};
print('${picked.length} different seats: $picked');
} Output
B12 is booked by Asha
B13 taken: true
B14 taken: false
2 different seats: {(row: C, seat: 1), (row: C, seat: 2)}
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run seat_map.dart
Each lookup in the output built a new record, and each found the entry stored earlier, or none:
| Lookup or action | Printed |
|---|---|
booked[(row: 'B', seat: 6 * 2)] |
Asha |
containsKey((seat: 13, row: 'B')), named fields swapped |
true |
containsKey((row: 'B', seat: 14)) |
false |
| a set literal with C1 twice and C2 once | 2 different seats |
The LinkedHashMap documentation explains why this works: a map literal such as booked is a LinkedHashMap, which finds
entries in a hash table, so its keys need an == and a hashCode that agree, with equal hash codes for equal keys.
The records specification requires exactly that of records, which makes any record whose fields compare soundly, such
as strings and ints, a dependable key. The seat (row: 'B', seat: 6 * 2) was computed at run time and still matched,
and the set dropped the repeated C1 without being told to.
Checkpoint
Check: Records as map keys and set elements
One function can return several values in a record
Back to the five readings from the start. min_max.dart returns the lowest and the highest reading together:
(int, int) minMax(List<int> xs) {
var lo = xs.first, hi = xs.first;
for (final x in xs.skip(1)) {
if (x < lo) lo = x;
if (x > hi) hi = x;
}
return (lo, hi);
}
void main() {
final temperatures = [31, 27, 35, 29, 33];
final (lo, hi) = minMax(temperatures);
print('lowest $lo, highest $hi');
final range = minMax(temperatures);
print('spread ${range.$2 - range.$1} degrees');
} Output
lowest 27, highest 35 spread 8 degrees
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run min_max.dart
The return type (int, int) promises exactly two ints, and return (lo, hi); builds the record. At the call site,
final (lo, hi) = minMax(temperatures); declares two variables in one statement and printed lowest 27, highest 35.
That declaration is a pattern, which the destructuring lesson takes apart in
full. The second call keeps the whole record and reads it through $1 and $2, which gave the spread of 8 degrees.
The records page puts the alternatives in one sentence: a class is more verbose, and a List or a Map loses type safety. This table compares the ways the same two results can come back:
| Return type | The caller receives | What the compiler checks |
|---|---|---|
List<int> |
[27, 35] |
that each value is an int, not how many there are or which is which |
Map<String, int> |
{'min': 27, 'max': 35} |
nothing about the keys |
a class MinMax |
an object with two fields | everything, after you declare the class |
(int, int) |
(27, 35) |
the number of values and the type of each |
({int min, int max}) |
(min: 27, max: 35) |
the number, the types and the names |
The last row is the safest when both values have the same type, because a caller writes range.min and cannot mix
them up. A record also groups futures of different types so that they can be awaited together, which the asynchronous
programming module shows.
Checkpoint
Check: Several return values in one record
A class or a map fits when a record cannot
Records only hold data. as_map.dart stores Pune’s coordinates in a map and in a record, and asks both the same questions:
void main() {
// The same two values as a map: the keys are strings, checked only at run time.
final pune = <String, double>{'lat': 18.52, 'lng': 73.86};
print('misspelt key: ${pune['lattitude']}');
final again = <String, double>{'lat': 18.52, 'lng': 73.86};
print('two equal maps ==: ${pune == again}');
// As a record, the same misspelling would not compile, and equal values are equal.
final asRecord = (lat: 18.52, lng: 73.86);
print('two equal records ==: ${asRecord == (lat: 18.52, lng: 73.86)}');
} Output
misspelt key: null two equal maps ==: false two equal records ==: true
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run as_map.dart
The misspelt key 'lattitude' compiled and quietly read null, and two maps with equal entries compared false, because
Map keeps the identity == that List has. The record would refuse the misspelling at compile time, and its two equal
values compared true. A class sits between them: it is checked like a record, and it can also run code, such as a
constructor that refuses a negative amount, which a record never can. Answer the three questions in the table for a
piece of data from your own code:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Shape | Checked by the compiler | Equal by contents | Methods, checks, rules | A public type that grows | Keys known at run time |
|---|---|---|---|---|---|
| Record Holds: fixed fields, each typed | Good every field name and type is checked, as named_shape.dart showed | Good == and hashCode are built in: the two records compared true | Poor no methods of its own and no constructor that can refuse a value | Poor another field is another shape, so another type | Poor the fields are fixed when the code is written |
| Class Holds: fields and code | Good fields and getters are checked, as on a record | Mixed only identical objects are equal, until you override == and hashCode | Good a constructor can refuse bad values, and methods keep the rules in one place | Good a new getter leaves code that uses the old ones compiling | Poor the fields are fixed when the class is written |
| Map Holds: keys chosen at run time | Poor a misspelt key compiles and reads null, as as_map.dart printed | Poor two maps with equal entries compared false | Poor nothing runs when an entry is added or changed | Mixed new keys compile anywhere, and nothing checks that they are there | Good any key, added or removed while the program runs |
How the answers decide
- “Does the data need methods, a check when a value is made, or a rule that must always hold?” Yes, it needs code: rules out Record and Map. Only a class runs your code when a value is made or used, for example to refuse a negative amount.
- “Will code outside your library depend on this type for a long time?” Yes, it is a public type: adds a caveat to Record. dart.dev notes that only a class gives full abstraction and encapsulation; a typedef only names a record shape.
- “Do you know every field name when you write the code?” No, they come from data: rules out Record and Class. Names that arrive with the data need a map, because a record or a class fixes its names when it is compiled.
- “Does the data need methods, a check when a value is made, or a rule that must always hold?” No, it only holds values and “Will code outside your library depend on this type for a long time?” No, it stays local and “Do you know every field name when you write the code?” Yes, they are fixed: adds a caveat to Class. A class works too, but for values that only travel together a record needs no declaration and brings its own ==.
In short: a record for values that only travel together inside your code, such as results, keys and pairs; a class when the data needs methods, checks or a long-lived public type; a map when the field names arrive with the data. The records page adds that a typedef only names a record’s shape, and that only a class gives full abstraction and encapsulation.
Checkpoint
Check: Record, class or map
Practise: two functions that return records
The package has three files: a pubspec whose one dev dependency is package:test, a library whose two functions still
throw UnimplementedError, and a test file whose four sample tests say what those functions must return.
practice/pubspec.yaml
name: practice
description: Two functions that return records, with four sample tests.
publish_to: none
environment:
sdk: ^3.13.0
dev_dependencies:
test: ^1.33.0 lib/practice.dart · practice/lib/practice.dart
/// The smallest value, the largest value and the mean of [xs],
/// or null when [xs] is empty.
({int min, int max, double mean})? stats(List<int> xs) {
throw UnimplementedError('stats');
}
/// Splits a handle such as 'asha@okbank' into its user part and its
/// handle part, or returns null when [vpa] does not follow the rule:
/// exactly one '@', with at least one character on each side.
(String user, String handle)? splitVpa(String vpa) {
throw UnimplementedError('splitVpa');
} test/practice_test.dart · practice/test/practice_test.dart
import 'package:practice/practice.dart';
import 'package:test/test.dart';
void main() {
test('stats of three readings', () {
expect(stats([4, 1, 7]), (min: 1, max: 7, mean: 4.0));
});
test('stats of no readings is null', () {
expect(stats([]), isNull);
});
test('a handle splits at the @', () {
expect(splitVpa('asha@okbank'), ('asha', 'okbank'));
});
test('a handle without a user part is refused', () {
expect(splitVpa('@okbank'), isNull);
});
} The session prints the starter file and then runs the tests:
from session import files, run
# The practice package as handed out: both functions still throw, so all four sample tests fail.
files("practice=records_practice")
run(
"""
cd records_practice
cat lib/practice.dart
dart pub get --offline > /dev/null
!dart test --reporter failures-only
""",
pub=True,
) Output
$ cd records_practice
$ cat lib/practice.dart
/// The smallest value, the largest value and the mean of [xs],
/// or null when [xs] is empty.
({int min, int max, double mean})? stats(List<int> xs) {
throw UnimplementedError('stats');
}
/// Splits a handle such as 'asha@okbank' into its user part and its
/// handle part, or returns null when [vpa] does not follow the rule:
/// exactly one '@', with at least one character on each side.
(String user, String handle)? splitVpa(String vpa) {
throw UnimplementedError('splitVpa');
}
$ dart pub get --offline > /dev/null
$ dart test --reporter failures-only
+0 -1: test/practice_test.dart: stats of three readings [E]
UnimplementedError: stats
package:practice/practice.dart 4:3 stats
test/practice_test.dart 6:12 main.<fn>
+0 -2: test/practice_test.dart: stats of no readings is null [E]
UnimplementedError: stats
package:practice/practice.dart 4:3 stats
test/practice_test.dart 10:12 main.<fn>
+0 -3: test/practice_test.dart: a handle splits at the @ [E]
UnimplementedError: splitVpa
package:practice/practice.dart 11:3 splitVpa
test/practice_test.dart 14:12 main.<fn>
+0 -4: test/practice_test.dart: a handle without a user part is refused [E]
UnimplementedError: splitVpa
package:practice/practice.dart 11:3 splitVpa
test/practice_test.dart 18:12 main.<fn>
+0 -4: Some tests failed.
Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'dart test --chain-stack-traces'.
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py
- Copy the package into a folder of your own:
pubspec.yaml,lib/practice.dartandtest/practice_test.dartas above. Rundart pub getand thendart test: all four tests fail, as in the session. - Write
stats. Return null for an empty list; otherwise build a record with the named fieldsmin,maxandmean, wheremeanis a double. - Write
splitVpaby the rule in its comment: exactly one ‘@’, with at least one character before and after it. Return a record of the two parts, or null. - Run
dart testuntil it ends with “All tests passed!”. Then add tests of your own for one reading, for negative readings, and for ‘a@’, ‘a@b@c’ and an empty string, and make them pass.
Once all four sample tests pass, check what stayed with you from each section:
Practice that adapts to you
Practise this lesson
12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): Two records hold the same values. When does == call them equal, and why does that make them safe map keys?
A record compares by contents, not by object. == first checks that the other value is a record of the same shape, the same number of positional fields and the same named fields, and then compares the matching fields with their own ==. The order in which named fields were written does not matter. Records also produce a hash code from their fields, so two equal records always hash alike, and a map or a set needs exactly that to find a key again: a record built later with the same row and seat finds the stored booking. The answer is only as good as the fields’ own equality, though. A field holding a List compares by identity, so two records with equal but separate lists are not equal, and such a record finds nothing when used as a key.
Two longer questions from the module’s bank build on this lesson:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A package exposes typedef Stats = ({int total, int count}) and a function that returns it. A new rule says count may never be negative, so Stats has to become a class. How do you make the change, and which code in your callers still breaks?
-
Mid to senior
A memoised function keeps its last key and result and checks identical(key, lastKey) before it looks in its Map<(int, int), int> cache. Profiling shows the fast path never hits, although callers repeat the same (row, col) pair. Why, and what would you change?
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
How do you read the fields of a Dart record?
Positional fields through the getters $1, $2 and so on, named fields by their names. A record is one immutable value holding a fixed set of fields.
-
Why does final wrapped = (amount); not make a record?
Parentheses alone do not make a record: wrapped gets amount's type, and wrapped.$1 does not compile. Write (amount,) with a trailing comma, or name the field: (amount: amount).
-
What makes up a record's type?
Its shape: the number of positional fields, the names of the named fields and the type of every field. Names written on positional fields are documentation, and a typedef only gives a shape a second name.
-
When are two records equal?
When their shapes match and every field is equal, each compared with its own ==; the order named fields were written in does not matter. A field holding a List therefore compares by identity.
-
Does final order = ('Thali', tags); copy the tags list?
No. The record holds the same list, so a later tags.add(...) shows up in order. Build the record from a copy when it must keep the values it had.
-
How can one Dart function return two typed values without a class?
With a record return type such as (int, int) or ({int min, int max}).
-
When does a class or a map fit better than a record?
A class when the data needs code or a long-lived public type, a map when the names come from the data itself.
Key takeaways
- A record bundles a fixed set of positional and named fields into one immutable value; positional fields are read as
$1,$2and so on, named ones by their names, and a record with one positional field needs a trailing comma. - A record’s type is its shape: the number of positional fields, the names of named fields and every field’s type, while names written on positional fields are documentation and a typedef only gives a shape a second name.
- Two records count as equal when their shapes match and every field is equal, each compared with its own
==, so a field holding a List compares by identity and can still change inside the record. - Records give matching
==andhashCode, which makes records of strings and numbers dependable map keys and set elements, as the seat map showed. - A record return type such as
(int, int)or({int min, int max})returns several typed values without a class; choose a class when the data needs code or a long-lived public type, and a map when names come from the data.
Terms in this lesson
- named field
- A field of a record written as name: value and read through a getter of the same name. Its name is part of the record's type, and the order of named fields is not.
- positional field
- A field of a record written without a name and read by its position, as $1, $2 and so on, counting only the positional fields from the left.
- record
- An anonymous, immutable value that bundles a fixed set of positional and named fields, each with its own type. Records need no declaration and compare equal when their shape and fields are equal.
- record shape
- The number of positional fields of a record, the names of its named fields and the type of every field. The shape alone decides a record's type.
- structural equality
- Equality decided by contents rather than by object: two records are equal when they have the same shape and every pair of corresponding fields is equal.
References
- Records (The Dart project (Google))
- Records feature specification (The Dart project (Google))
- Record class (The Dart project (Google))
- Typedefs (The Dart project (Google))
- List operator == (The Dart project (Google))
- LinkedHashMap class (The Dart project (Google))
Report a problem with this lesson
Kept only in this browser. Your Learn progress