Skip to content

Commit f02a945

Browse files
committed
Merge remote-tracking branch 'varkor/destructuring-assignment'
2 parents c849288 + 58091b6 commit f02a945

File tree

1 file changed

+384
-0
lines changed

1 file changed

+384
-0
lines changed

text/0000-destructuring-assignment.md

Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
- Feature Name: `destructuring_assignment`
2+
- Start Date: 2020-04-17
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
4+
- Rust Issue: [rust-lang/rust#71126](https://github.com/rust-lang/rust/issues/71126)
5+
- Proof-of-concept: [rust-lang/rust#71156](https://github.com/rust-lang/rust/pull/71156)
6+
7+
# Summary
8+
[summary]: #summary
9+
10+
We allow destructuring on assignment, as in `let` declarations. For instance, the following are now
11+
accepted:
12+
13+
```rust
14+
(a, (b.x.y, c)) = (0, (1, 2));
15+
(x, y, .., z) = (1.0, 2.0, 3.0, 4.0, 5.0);
16+
[_, f, *baz(), a[i]] = foo();
17+
[g, _, h, ..] = ['a', 'w', 'e', 's', 'o', 'm', 'e', '!'];
18+
Struct { x: a, y: b } = bar();
19+
Struct { x, y } = Struct { x: 5, y: 6 };
20+
```
21+
22+
This brings assignment in line with `let` declaration, in which destructuring is permitted. This
23+
will simplify and improve idiomatic code involving mutability.
24+
25+
# Motivation
26+
[motivation]: #motivation
27+
28+
Destructuring assignment increases the consistency of the language, in which assignment is typically
29+
expected to behave similarly to variable declarations. The aim is that this feature will increase
30+
the clarity and concision of idiomatic Rust, primarily in code that makes use of mutability. This
31+
feature is [highly desired among Rust developers](https://github.com/rust-lang/rfcs/issues/372).
32+
33+
# Guide-level explanation
34+
[guide-level-explanation]: #guide-level-explanation
35+
36+
You may destructure a value when making an assignment, just as when you declare variables. See the
37+
[Summary](#Summary) for examples. The following structures may be destructured:
38+
39+
- Tuples.
40+
- Slices.
41+
- Structs (including unit and tuple structs).
42+
- Unique variants of enums.
43+
44+
You may use `_` and `..` as in a normal declaration pattern to ignore certain values.
45+
46+
# Reference-level explanation
47+
[reference-level-explanation]: #reference-level-explanation
48+
49+
The feature as described here has been implemented as a proof-of-concept
50+
(https://github.com/rust-lang/rust/pull/71156). It follows essentially the [suggestions of
51+
@Kimundi](https://github.com/rust-lang/rfcs/issues/372#issuecomment-214022963) and [of
52+
@drunwald](https://github.com/rust-lang/rfcs/issues/372#issuecomment-262519146).
53+
54+
The Rust compiler already parses complex expressions on the left-hand side of an assignment, but
55+
does not handle them other than emitting an error later in compilation. We propose to add
56+
special-casing for several classes of expressions on the left-hand side of an assignment, which act
57+
in accordance with destructuring assignment: i.e. as if the left-hand side were actually a pattern.
58+
Actually supporting patterns directly on the left-hand side of an assignment significantly
59+
complicates Rust's grammar and it is not clear that it is even technically feasible. Conversely,
60+
handling some classes of expressions is much simpler, and is indistinguishable to users, who will
61+
receive pattern-oriented diagnostics due to the desugaring of expressions into patterns.
62+
63+
To describe the context of destructuring assignments more precisely, we add a new class of
64+
expressions, which we call "assignee expressions".
65+
Assignee expressions are analogous to [place
66+
expressions](https://doc.rust-lang.org/reference/expressions.html#place-expressions-and-value-expressions)
67+
(also called "lvalues") in that they refer to expressions representing a memory location, but may
68+
only appear on the left-hand side of an assignment (unlike place expressions). Every place
69+
expression is also an assignee expression.
70+
71+
The class of assignee expressions is defined inductively:
72+
73+
- Place: `place`.
74+
- Underscore: `_`.
75+
- Tuples: `(assignee, assignee, assignee)`, `(assignee, .., assignee)`, `(.., assignee, assignee)`, `(assignee, assignee, ..)`.
76+
- Slices: `[assignee, assignee, assignee]`, `[assignee, .., assignee]`, `[.., assignee, assignee]`, `[assignee, assignee, ..]`.
77+
- Tuple structs: `path(assignee, assignee, assignee)`, `path(assignee, .., assignee)`, `path(.., assignee, assignee)`,
78+
`path(assignee, assignee, ..)`.
79+
- Structs: `path { field: assignee, field: assignee }`, `path { field: assignee, field: assignee, .. }`.
80+
- Unit structs: `path`.
81+
82+
The place expression "The left operand of an assignment or compound assignment expression." ibid.
83+
is changed to "The left operand of a compound assignment expression.", while
84+
"The left operand of an assignment expression." is now an assignee expression.
85+
86+
The general idea is that we will desugar the following complex assignments as demonstrated.
87+
88+
```rust
89+
(a, b) = (3, 4);
90+
91+
[a, b] = [3, 4];
92+
93+
Struct { x: a, y: b } = Struct { x: 3, y: 4};
94+
95+
// desugars to:
96+
97+
{
98+
let (_a, _b) = (3, 4);
99+
a = _a;
100+
b = _b;
101+
}
102+
103+
{
104+
let [_a, _b] = [3, 4];
105+
a = _a;
106+
b = _b;
107+
}
108+
109+
{
110+
let Struct { x: _a, y: _b } = Struct { x: 3, y: 4};
111+
a = _a;
112+
b = _b;
113+
}
114+
```
115+
116+
Note that the desugaring ensures that destructuring assignment, like normal assignment, is an
117+
expression.
118+
119+
We support the following classes of expressions:
120+
121+
- Tuples.
122+
- Slices.
123+
- Structs (including unit and tuple structs).
124+
- Unique variants of enums.
125+
126+
In the desugaring, we convert the expression `(a, b)` into an analogous pattern `(_a, _b)` (whose
127+
identifiers are fresh and thus do not conflict with existing variables). A nice side-effect is that
128+
we inherit the diagnostics for normal pattern-matching, so users benefit from existing diagnostics
129+
for destructuring declarations.
130+
131+
Nested structures may be destructured, for instance:
132+
133+
```rust
134+
let (a, b, c);
135+
((a, b), c) = ((1, 2), 3);
136+
137+
// desugars to:
138+
139+
let (a, b, c);
140+
{
141+
let ((_a, _b), _c) = ((1, 2), 3);
142+
a = _a;
143+
b = _b;
144+
c = _c;
145+
};
146+
```
147+
148+
We also allow arbitrary parenthesisation, as with patterns, although unnecessary parentheses will
149+
trigger the `unused_parens` lint.
150+
151+
Note that `#[non_exhaustive]` must be taken into account properly: enums marked `#[non_exhaustive]`
152+
may not have their variants destructured, and structs marked `#[non_exhaustive]` may only be
153+
destructured using `..`.
154+
155+
Patterns must be irrefutable. In particular, only slice patterns whose length is known at compile-
156+
time, and the trivial slice `[..]` may be used for destructuring assignment.
157+
158+
Unlike in usual `let` bindings, default binding modes do *not* apply for the desugared destructuring
159+
assignments, as this leads to counterintuitive behaviour since the desugaring is an implementation
160+
detail.
161+
162+
## Diagnostics
163+
164+
It is worth being explicit that, in the implementation, the diagnostics that are reported are
165+
pattern diagnostics: that is, because the desugaring occurs regardless, the messages will imply that
166+
the left-hand side of an assignment is a true pattern (the one the expression has been converted
167+
to). For example:
168+
169+
```rust
170+
[*a] = [1, 2]; // error: pattern requires 1 element but array has 2
171+
```
172+
173+
Whilst `[*a]` is not strictly speaking a pattern, it behaves similarly to one in this context. We
174+
think that this results in a better user experience, as intuitively the left-hand side of a
175+
destructuring assignment acts like a pattern "in spirit", but this is technically false: we should
176+
be careful that this does not result in misleading diagnostics.
177+
178+
## Underscores and ellipses
179+
180+
In patterns, we may use `_` and `..` to ignore certain values, without binding them. While range
181+
patterns already have analogues in terms of range expressions, the underscore wildcard pattern
182+
currently has no analogous expression. We thus add one, which is only permitted in the left-hand side
183+
of an assignment: any other use results in the same "reserved identifier" error that currently
184+
occurs for invalid uses of `_` as an expression. A consequence is that the following becomes valid:
185+
186+
```rust
187+
_ = 5;
188+
```
189+
190+
Functional record update syntax (i.e. `..x`) is forbidden in destructuring assignment, as we believe
191+
there is no sensible and clear semantics for it in this setting. This restriction could be relaxed
192+
in the future if a use-case is found.
193+
194+
The desugaring treats the `_` expression as an `_` pattern and the fully empty range `..` as a `..`
195+
pattern. No corresponding assignments are generated. For example:
196+
197+
```rust
198+
let mut a;
199+
(a, _) = (3, 4);
200+
(.., a) = (1, 2, 3, 4);
201+
202+
// desugars to:
203+
204+
{
205+
let (_a, _) = (3, 4);
206+
a = _a;
207+
}
208+
209+
{
210+
let (.., _a) = (1, 2, 3, 4);
211+
a = _a;
212+
}
213+
```
214+
215+
and similarly for slices and structs.
216+
217+
## Unsupported patterns
218+
219+
We do not support the following "patterns" in destructuring assignment:
220+
221+
- `&x = foo();`.
222+
- `&mut x = foo();`.
223+
- `ref x = foo();`.
224+
- `x @ y = foo()`.
225+
- (`box` patterns, which are deprecated.)
226+
227+
This is primarily for learnability: the behaviour of `&` can already be slightly confusing to
228+
newcomers, as it has different meanings depending on whether it is used in an expression or pattern.
229+
In destructuring assignment, the left-hand side of an assignment consists of sub*expressions*, but
230+
which act intuitively like patterns, so it is not clear what `&` and friends should mean. We feel it
231+
is more confusing than helpful to allow these cases. Similarly, although coming up with a sensible
232+
meaning for `@`-bindings in destructuring assignment is not inconceivable, we believe they would be
233+
confusing at best in this context. Conversely, destructuring tuples, slices or structs is very
234+
natural and we do not foresee confusion with allowing these.
235+
236+
Our implementation is forwards-compatible with allowing these patterns in destructuring assigmnent,
237+
in any case, so we lose nothing by not allowing them from the start.
238+
239+
Additionally, we do not give analogues for any of the following, which make little sense in this
240+
context:
241+
242+
- Literal patterns.
243+
- Range patterns.
244+
- Or patterns.
245+
246+
Therefore, literals, bitwise OR, and range expressions (`..`, `..=`) are not permitted on the
247+
left-hand side of a destructuring assignment.
248+
249+
## Compound destructuring assignment
250+
251+
We forbid destructuring compound assignment, i.e. destructuring for operators like `+=`, `*=` and so
252+
on. This is both for the sake of simplicity and since there are relevant design questions that do
253+
not have obvious answers, e.g. how this could interact with custom implementations of the operators.
254+
255+
## Order-of-assignment
256+
257+
The right-hand side of the assignment is always evaluated first. Then, assignments are performed
258+
left-to-right. Note that component expressions in the left-hand side may be complex, and not simply
259+
identifiers.
260+
261+
In a declaration, each identifier may be bound at most once. That is, the following is invalid:
262+
263+
```rust
264+
let (a, a) = (1, 2);
265+
```
266+
267+
For destructuring assignments, we currently permit assignments containing identical identifiers. However, these trigger an "unused assignment"
268+
warning.
269+
270+
```rust
271+
(a, a) = (1, 2); // warning: value assigned to `a` is never read
272+
assert_eq!(a, 2);
273+
```
274+
275+
We could try to explicitly forbid this. However, the chosen behaviour is justified in two ways:
276+
- A destructuring
277+
assignment can always be written as a series of assignments, so this behaviour matches its
278+
expansion.
279+
- In general, we are not able to tell when overlapping
280+
assignments are made, so the error would be fallible. This is illustrated by the following example:
281+
282+
```rust
283+
fn foo<'a>(x: &'a mut u32) -> &'a mut u32 {
284+
x
285+
}
286+
287+
fn main() {
288+
let mut x: u32 = 10;
289+
// We cannot tell that the same variable is being assigned to
290+
// in this instance.
291+
(*foo(&mut x), *foo(&mut x)) = (5, 6);
292+
assert_eq!(x, 6);
293+
}
294+
```
295+
296+
We thus feel that a lint is more appropriate.
297+
298+
# Drawbacks
299+
[drawbacks]: #drawbacks
300+
301+
- It could be argued that this feature increases the surface area of the language and thus
302+
complexity. However, we feel that by decreasing surprise, it actually makes the language less
303+
complex for users.
304+
- It is possible that these changes could result in some confusing diagnostics. However, we have not
305+
found any during testing, and these could in any case be ironed out before stabilisation.
306+
307+
# Rationale and alternatives
308+
[rationale-and-alternatives]: #rationale-and-alternatives
309+
310+
As we argue above, we believe this change increases the perceived consistency of Rust and improves
311+
idiomatic code in the presence of mutability, and that the
312+
implementation is simple and intuitive.
313+
314+
One potential alternative that has been put forth in the past is to allow arbitrary patterns on the
315+
left-hand side of an assignment, but as discussed above and [extensively in this
316+
thread](https://github.com/rust-lang/rfcs/issues/372), it is difficult to see how this could work in
317+
practice (especially with complex left-hand sides that do not simply involve identifiers) and it is
318+
not clear that this would have any advantages.
319+
320+
Another suggested alternative is to introduce a new keyword for indicating an assignment to an
321+
existing expression during a `let` variable declaration. For example, something like the following:
322+
323+
```rust
324+
let (a, reassign b) = expr;
325+
```
326+
327+
This has the advantage that we can reuse the existing infrastructure for patterns. However, it has
328+
the following disadvantages, which we believe make it less suitable than our proposal:
329+
330+
- It requires a new keyword or overloading an existing one, both of which have syntactic and
331+
semantic overhead.
332+
- It is something that needs to be learnt by users: conversely, we maintain that it is natural to
333+
attempt destructuring assignment with the syntax we propose already, so does not need to be
334+
learnt.
335+
- It changes the meaning of `let` (which has previously been associated only with binding new
336+
variables).
337+
- To be consistent, we ought to allow `let reassign x = value;`, which introduces another way
338+
to simply write `x = value;`.
339+
- It is longer and no more readable than the proposed syntax.
340+
341+
# Prior art
342+
[prior-art]: #prior-art
343+
344+
The most persuasive prior art is Rust itself, which already permits destructuring declarations.
345+
Intuitively, a declaration is an assignment that also introduces a new binding. Therefore, it seems
346+
clear that assignments should act similarly to declarations where possible. However, it is also the
347+
case that destructuring assignments are present in many languages that permit destructuring
348+
declarations.
349+
350+
- JavaScript
351+
[supports destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment).
352+
- Python [supports destructuring assignment](https://blog.tecladocode.com/destructuring-in-python/).
353+
- Perl
354+
[supports destructuring assignment](https://perl6advent.wordpress.com/2017/12/05/day-5-destructure-your-arguments-with-perl-6-signatures/).
355+
- And so on...
356+
357+
It is a general pattern that languages support destructuring assignment when they support
358+
destructuring declarations.
359+
360+
# Unresolved questions
361+
[unresolved-questions]: #unresolved-questions
362+
363+
None.
364+
365+
# Future possibilities
366+
[future-possibilities]: #future-possibilities
367+
368+
- The implementation already supports destructuring of every class of expressions that currently
369+
make sense in Rust. This feature naturally should be extended to any new class of expressions for
370+
which it makes sense.
371+
- It could make sense to permit [destructuring compound
372+
assignments](#Compound-destructuring-assignment) in the future, though we defer this question for
373+
later discussions.
374+
- It could make sense to permit [`ref` and `&`](#Unsupported-patterns) in the future.
375+
- It [has been suggested](https://github.com/rust-lang/rfcs/issues/372#issuecomment-365606878) that
376+
mixed declarations and assignments could be permitted, as in the following:
377+
378+
```rust
379+
let a;
380+
(a, let b) = (1, 2);
381+
assert_eq!((a, b), (1, 2));
382+
```
383+
384+
We do not pursue this here, but note that it would be compatible with our desugaring.

0 commit comments

Comments
 (0)