DMarlon Studio

Lesson 10 · 10 of 20

Records and destructuring · Dart

Return several values without writing a class using Dart 3 records, and destructure them into variables, in loops and in swaps.

Dart · Records and destructuring in DMarlon Studio: the example's code and its output in the terminal

A record groups several values without needing a class. It can have positional fields, like (double, double, double), or named ones, like ({String city, double celsius}). The typedef gives the latter a short name, Reading, which is then used like any other type.

summarize returns three values at once: the minimum, the maximum and the average. In main, final (low, high, avg) = ... destructures them: it is a pattern that unpacks the record into three variables in one go. Before Dart 3 this would have needed a helper class or an untyped list.

In the for loop, the pattern (:city, :celsius) pulls the named fields into variables with the same names; :city is short for city: city. The loop then reads almost like a sentence and only warns about the cities above 30 degrees.

Named fields are read by name (first.city) and positional ones with $1, $2…, which is why summarize([1, 2]).$2 is the maximum, 2.0. Records have value equality: two records with the same fields are == even when created separately. Finally, (a, b) = (b, a) swaps two variables without a temporary one.

Code · main.dart

// A record type with named fields, given a short name.typedef Reading = ({String city, double celsius});// Returning a positional record: three values, no helper class.(double, double, double) summarize(List<double> values) {  final sorted = [...values]..sort();  final average = values.reduce((a, b) => a + b) / values.length;  return (sorted.first, sorted.last, average);}void main() {  final List<Reading> readings = [    (city: 'Madrid', celsius: 31.5),    (city: 'Bilbao', celsius: 22.0),    (city: 'Seville', celsius: 36.2),    (city: 'Vigo', celsius: 19.8),  ];  // Destructure the result into three variables at once.  final (low, high, avg) = summarize([for (final r in readings) r.celsius]);  print('Low $low, high $high, average ${avg.toStringAsFixed(1)}');  // :city is short for city: city (take the field into a variable).  for (final (:city, :celsius) in readings) {    if (celsius > 30) print('Heat warning in $city ($celsius C)');  }  final first = readings.first;  print('Fields: ${first.city}, positional: ${summarize([1, 2]).$2}');  print('Equal by value: ${(city: 'Vigo', celsius: 19.8) == readings[3]}');  var (a, b) = ('left', 'right');  (a, b) = (b, a); // swap without a temporary variable  print('Swapped: $a $b');}

Output · dart run main.dart

Low 19.8, high 36.2, average 27.4
Heat warning in Madrid (31.5 C)
Heat warning in Seville (36.2 C)
Fields: Madrid, positional: 2.0
Equal by value: true
Swapped: right left

Key points

  • A record groups values without a class: (1, 'a') or (x: 1, y: 2).
  • Returning a record is the natural way to return several values.
  • final (a, b) = record; destructures it into variables.
  • :field pulls a named field into a variable of the same name.
  • Records are compared by value.

Exercise

Make summarize return a named record, ({double min, double max, double avg}), and change the destructuring to final (:min, :max, :avg) = ....