In Dart 3, how do you pull the fields of a returned record or an object's getters into local variables with a pattern?
answer
- starts with var or final
- record pattern mirrors the record
- :name reuses the field name
- object pattern calls getters
- _ skips a field
basics
~20 sWrite a pattern variable declaration: var (city, temp) = latestReading(); for a positional record, final (:city, :temp) = ... for named fields, and var Forecast(:high, :low) = forecast; to read an object's getters into new locals.
solid answer
~40 sA **pattern variable declaration** starts with `var` or `final`, followed by a pattern that mirrors the value's shape. For a positional record, `var (city, temp) = latestReading();` binds two new locals. For named fields, `final (city: name, temp: t) = reading;` renames them, and `final (:city, :temp) = reading;` keeps the field names. **Object patterns** work on any class through its getters: `var Forecast(:high, :low) = forecast;` calls `high` and `low` and binds them. The wildcard `_` skips a part you do not need, as in `var (city, _) = latestReading();`. The same patterns work in `for-in` loops, such as `for (var MapEntry(:key, :value) in temps.entries)`, and in assignments to existing variables, which gives the one-line swap `(a, b) = (b, a);`.
code
dart · 25 linesclass Forecast {
Forecast(this.city, this.high, this.low);
final String city;
final double high;
final double low;
}
(String, double) latestReading() => ('Oslo', 3.5);
void main() {
var (city, temp) = latestReading();
print('$city $temp'); // Oslo 3.5
var Forecast(:high, :low) = Forecast('Oslo', 7.0, -1.5);
print(high - low); // 8.5
final temps = {'Oslo': 3.5, 'Rome': 18.0};
for (var MapEntry(key: place, :value) in temps.entries) {
print('$place: $value');
}
var (a, b) = ('left', 'right');
(a, b) = (b, a); // swap existing variables
print('$a $b'); // right left
}go deeper
Be able to write var (city, temp) = latestReading(); and final (:city, :temp) = reading(); and read a MapEntry with a for-in pattern.
Explain declaration versus assignment patterns, why record patterns must cover the whole shape while object patterns read only named getters, and what :name expands to.
Know when not to destructure: data whose shape is not guaranteed belongs in an if-case or switch, where a mismatch can fail instead of throwing.
Set readability conventions for destructuring in shared code, such as preferring named record fields so declarations read as :city rather than positional guesses.
## Patterns as the left-hand side Since Dart 3.0, a **pattern** can appear where you would normally write a variable name. In a **pattern variable declaration**, the pattern describes the shape of the value on the right, Dart takes that value apart, and every variable in the pattern becomes a new local. The declaration must start with **`var`** or **`final`**; types, if you want them, go inside the pattern. ## Records: positional and named A weather screen often gets a record back from a helper: ```dart (String city, double temp) latestReading() => ('Oslo', 3.5); var (city, temp) = latestReading(); // city: String, temp: double ``` This is equivalent to reading `$1` and `$2` into two variables, but in one line and with both types inferred. For named fields, write the field name, a colon and the variable: ```dart ({String city, double temp}) reading() => (city: 'Oslo', temp: 3.5); final (city: name, temp: t) = reading(); // renamed final (:city, :temp) = reading(); // same names as the fields ``` The `:city` form is shorthand for `city: city`. A **record pattern** must match the **whole record**: the pattern needs a subpattern for every field, so use `_` for the ones you do not need. ## Objects: any class, through its getters An **object pattern** names a type and lists getters to read: ```dart class Forecast { Forecast(this.city, this.high, this.low); final String city; final double high; final double low; } final forecast = Forecast('Oslo', 7.0, -1.5); var Forecast(:high, :low) = forecast; print(high - low); // 8.5 ``` Unlike a record pattern, an object pattern does **not** need to mention every property: `city` is simply not read. The class needs no special support — any getter, including a computed one, can be destructured. ## The other places declarations appear | Where | Example | What it does | |---|---|---| | local declaration | `var (city, temp) = latestReading();` | binds new locals | | `for-in` loop | `for (var MapEntry(:key, :value) in temps.entries)` | destructures each element | | `for` loop initializer | `for (var (i, sum) = (0, 0.0); i < n; i++)` | several typed loop variables at once | | assignment | `(a, b) = (b, a);` | assigns to **existing** variables | The assignment form is the idiomatic swap: the right-hand record is built first from the old values, then destructured into `a` and `b`. ## Wildcards and types - **`_`** matches anything and binds nothing: `var (city, _) = latestReading();`. - A **typed** subpattern declares the variable's type: `final (String city, double temp) = latestReading();`. - Nesting works as deep as the value: `var (city, (high, low)) = ('Oslo', (7.0, -1.5));`. ## Common mistakes - **Forgetting `var` or `final`.** `(city, temp) = latestReading();` is an assignment pattern and fails to compile unless `city` and `temp` already exist. - **Using list brackets on a record.** `var [city, temp] = ...` is a list pattern; a record needs parentheses. - **Mixing up named and positional.** `var (city, temp) = reading;` against `({String city, double temp})` is a shape mismatch; named fields need `city:` or `:city`. - **Expecting an object pattern to construct.** `Forecast(:high)` on the left reads getters; it never calls a constructor. - **Destructuring only to rename.** If a local just repeats `forecast.high`, the pattern adds nothing. ## When not to destructure Destructuring is for **reading parts into locals**. If you only need one field once, `reading.temp` is just as clear. If the value might not have the expected shape — data from the network, a nullable field, a value of a broader static type — a declaration is the wrong tool, because it cannot fail quietly; that is what `if-case` and switch cases are for. In a declaration, Dart checks the shape at compile time wherever the static types allow it, so a two-variable record pattern against a function that returns a three-field record is a compile error rather than a surprise at run time.
- What is the difference between `var (a, b) = pair;` and `(a, b) = pair;`?The first is a pattern variable declaration: it creates two new locals. The second is a pattern assignment: `a` and `b` must already exist, and the pattern assigns to them. That is why `(a, b) = (b, a);` swaps two variables without a temporary.
- Why can `var Forecast(:high) = forecast;` ignore the other properties when a record pattern cannot?An object pattern reads only the getters it names, so leaving properties out is fine. A record pattern matches the record's whole shape — every positional field and every named field — so each needs a subpattern, and `_` stands in for the ones you skip.
saying these in an interview costs you the question
- Writes (city, temp) = latestReading(); to declare new variables
- Thinks object patterns need a special constructor or method on the class
- Believes a record pattern may omit fields it does not need
- Reads $1 and $2 into temporaries instead of destructuring
- Thinks :city only works for records, not object patterns