|
| 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