Destructuring
A let or var may bind a pattern instead of a single name. The pattern mirrors the shape of the value and takes it apart, declaring one new binding for each name it contains:
let (quotient, remainder) = Divide(17, 5);
let Point { x: px, y: py } = point;
binding-pattern = Name
| "_"
| "(" binding-pattern { "," binding-pattern } ")"
| TypeName "{" field-pattern { "," field-pattern } "}"
field-pattern = Name [":" binding-pattern]
Irrefutable patterns only
A let has no second branch to take when a pattern does not fit, so its pattern must match every value of its type. It is built only from names, _, tuple patterns and structure patterns, nested to any depth. A literal, a range, an enum or variant case, a typed pattern, none or a presence pattern could fail, and is refused:
let (s, 1) = (1, 1);
error: refutable pattern in 'let' binding
note: a 'let' pattern must match every value, so each part is a name, '_', a tuple, or a structure
help: test the value with 'match' instead
To test a value against a pattern that may not match, use match.
Tuple patterns
A tuple pattern has one element per member of the tuple, in order:
let (quotient, remainder) = Divide(17, 5);
let ((x1, y1), (x2, y2)) = ((0, 0), (6, 8));
The pattern must have exactly as many elements as the tuple. let (a, b) = (1, 2, 3); is error: tuple pattern has 2 elements but type '(int, int, int)' has 3, and a tuple pattern over anything that is not a tuple, as in let (x, y) = 5;, is error: cannot destructure non-tuple type 'int'.
Structure patterns
A structure pattern names the type and lists fields by name, in any order. field: pattern binds the field through a pattern; field alone is shorthand for field: field, binding a variable with the field's own name:
let point = Point { x: 3, y: 4 };
let Point { x: px, y: py } = point;
let Point { y } = point;
A field the pattern does not mention is simply not bound, so there is no need to list every field. Structure and tuple patterns nest inside each other:
let segment = Segment { start: Point { x: 1, y: 2 }, end: Point { x: 5, y: 6 } };
let Segment { start: Point { x: sx }, end } = segment;
This binds sx to 1 and end to the whole end point. The type in the pattern must be the value's type — let Handle { code } = point; is error: struct pattern 'Handle' cannot match value of type 'Point' — and every field must exist: let Point { z } = point; is error: struct 'Point' has no field 'z'.
var patterns
With var, every name the pattern declares is mutable:
var (low, top) = (1, 9);
low -= 1;
top += 1;
There is no way to make some of the names mutable and others not.
Annotations
A type annotation after the pattern applies to the whole value:
let (pair, count): ((int, int), int) = ((1, 2), 3);
A pattern cannot annotate one of its own parts: let (a: int32, b) = … is a typed pattern, and typed patterns are refutable.
Discarding a value
_ matches anything and binds nothing. Inside a pattern it skips a part; on its own, let _ = value; evaluates a value and throws it away at once, like a temporary nobody kept:
let (_, high) = Bounds(readings);
let _ = Divide(1, 1);
_ names nothing, so it cannot be read afterwards — return _; is error: cannot read '_', because it discards the value it binds — and several let _ lines in one block do not clash.
A fallible result is the exception. Binding it to _ would lose its failure without a word, so binding one to _ is refused:
error: fallible result of type 'int ! ConfigError' is discarded
note: binding a fallible to '_' does not handle its failure
help: propagate it with '?', recover with 'catch', or match both '.Success' and '.Failure'
Handle the failure instead, as Handling errors shows.
Copying and moving patterns
A pattern initialized with = copies the parts it binds and leaves the source as it was. One initialized with <- takes the value over: each bound part moves into its binding, and each part bound to _ or left out of a structure pattern is destroyed on the spot.
A type that declares its own destructor is destroyed whole, so it cannot be split into parts:
let File { handle: h } <- file;
error: cannot split 'File' with a moving pattern, because it declares destructor '~File'
note: '~File' runs on the whole value, so no part of it can be taken out on its own
help: bind the whole value and read its fields, or match a value that stays with its owner
See Copy and move for what a move does to the source.
Patterns only declare
Destructuring always declares new names. It cannot assign to names that already exist, so a swap written as (m, n) = (n, m); is error: operator '=' requires an assignable target, but its left operand has type '(int, int)'. Assign the variables one at a time, through a temporary.
Each name in a pattern is its own declaration, so let (p, p) = (1, 2); is error: variable 'p' is already declared in this scope.
A for loop variable is a single name, never a pattern — see Loops.
See also
- Patterns — every pattern form, refutable ones included
- match — testing a value against patterns that may fail
- Tuples and Structures — the values being taken apart
- Learn: Destructure, Struct pattern