From 0c9e3d801493b812eb1c2080c65e9c93adcaa548 Mon Sep 17 00:00:00 2001
From: Novus Nota <68142933+novusnota@users.noreply.github.com>
Date: Wed, 6 Nov 2024 15:35:20 +0100
Subject: [PATCH] feat(docs): destructuring statement (#964)
---
CHANGELOG.md | 2 +-
docs/src/content/docs/book/statements.mdx | 155 ++++++++++++++++++++++
2 files changed, 156 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3fe5c5fa5..a8b387f58 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Docs: Google Analytics tags per every page: PR [#921](https://github.com/tact-lang/tact/pull/921)
- Docs: Added NFTs cookbook: PR [#958](https://github.com/tact-lang/tact/pull/958)
- Ability to specify a compile-time method ID expression for getters: PR [#922](https://github.com/tact-lang/tact/pull/922) and PR [#932](https://github.com/tact-lang/tact/pull/932)
-- Destructuring of structs and messages: PR [#856](https://github.com/tact-lang/tact/pull/856), PR [#969](https://github.com/tact-lang/tact/pull/969)
+- Destructuring of structs and messages: PR [#856](https://github.com/tact-lang/tact/pull/856), PR [#964](https://github.com/tact-lang/tact/pull/964), PR [#969](https://github.com/tact-lang/tact/pull/969)
- Docs: automatic links to Web IDE from all code blocks: PR [#994](https://github.com/tact-lang/tact/pull/994)
- The `SendDefaultMode` send mode constant to the standard library: PR [#1010](https://github.com/tact-lang/tact/pull/1010)
- Docs: initial semi-automated Chinese translation of the documentation: PR [#942](https://github.com/tact-lang/tact/pull/942)
diff --git a/docs/src/content/docs/book/statements.mdx b/docs/src/content/docs/book/statements.mdx
index 4db01b93c..ce363f5a3 100644
--- a/docs/src/content/docs/book/statements.mdx
+++ b/docs/src/content/docs/book/statements.mdx
@@ -3,6 +3,8 @@ title: Statements
description: "This page lists all the statements in Tact, which can appear anywhere in the function bodies"
---
+import { Badge } from '@astrojs/starlight/components';
+
The following statements can appear anywhere in the [function](/book/functions) body.
## `let` statement {#let}
@@ -96,6 +98,157 @@ value += 5; // augmented assignment (one of the many, see below)
:::
+## Destructuring assignment
+
+
+
+The destructuring assignment is a concise way to unpack [Structs][s] and [Messages][m] into distinct variables. It mirrors the [instantiation syntax](/book/expressions#instantiation), but instead of creating a new [Struct][s] or [Message][m] it binds every field or some of the fields to their respective variables.
+
+The syntax is derived from the [`let` statement](#let), and instead of specifying the variable name directly it involves specifying the structure type on the left side of the [assignment operator `={:tact}`](/book/operators#assignment), which corresponds to the structure type of the value on the right side.
+
+```tact {6}
+// Definition of Example
+struct Example { number: Int }
+
+// An arbitrary helper function
+fun get42(): Example { return Example { number: 42 } }
+
+fun basic() {
+ // Basic syntax of destructuring assignment (to the left of "="):
+ let Example { number } = get42();
+ // ------- ------ -------
+ // ↑ ↑ ↑
+ // | | gives the Example Struct
+ // | definition of "number" variable, derived
+ // | from the field "number" in Example Struct
+ // target structure type "Example"
+ // to destructure fields from
+
+ // Same as above, but with an instantiation
+ // to showcase how destructuring syntax mirrors it:
+ let Example { number } = Example { number: 42 };
+ // ----------------------
+ // ↑
+ // instantiation of Example Struct
+
+ // Above examples of syntax are roughly equivalent
+ // to the following series of statements:
+ let example = Example { number: 42 };
+ let number = example.number;
+}
+```
+
+Just like in [instantiation](/book/expressions#instantiation), the trailing comma is allowed.
+
+```tact
+struct Example { number: Int }
+
+fun trailblazing() {
+ let Example {
+ number, // trailing comma inside variable list
+ } = Example {
+ number: 42, // trailing comma inside field list
+ };
+}
+```
+
+:::note
+
+ [Augmented assignment operators](/book/operators#augmented-assignment) do not make sense for such assignments and will therefore be reported as parsing errors:
+
+ ```tact
+ struct Example { number: Int }
+ fun get42(): Example { return Example { number: 42 } }
+
+ fun basic() {
+ let Example { number } += get42();
+ // ^ this will result in the parse error:
+ // expected "="
+ }
+ ```
+
+:::
+
+To create a binding under a different variable name, specify it after the semicolon `:{:tact}`.
+
+```tact
+// Similar definition, but this time field is called "field", not "number"
+struct Example { field: Int }
+
+fun naming(s: Example) {
+ let Example { field: varFromField } = s;
+ // ------------ ↑
+ // ↑ |
+ // | instance of Example Struct, received
+ // | as a parameter of the function "naming"
+ // definition of "varFromField" variable, derived
+ // from the field "field" in Example Struct
+}
+```
+
+Note, that the order of bindings doesn't matter — all the fields retain their values and types under their names no matter the order in which they stand in their definition in the respective [Struct][s] or [Message][m].
+
+```tact
+// "first" goes first, then goes "second"
+struct Two { first: Int; second: String }
+
+fun order(s: Two) {
+ let Two { second, first } = s;
+ // ------ -----
+ // ↑ ↑
+ // | this variable will be of type Int,
+ // | same as the "first" field on Struct Two
+ // this variable will be of type String,
+ // same as the "second" field in Struct Two
+}
+```
+
+Destructuring assignment is exhaustive and requires specifying all the fields as variables. To deliberately ignore some of the fields, use an underscore `_{:tact}`, which discards the considered field's value. Note, that such wildcard variable name `_{:tact}` cannot be accessed:
+
+```tact
+// "first" goes first, then goes "second"
+struct Two { first: Int; second: String }
+
+fun discard(s: Two) {
+ let Two { second: _, first } = s;
+ // ---
+ // ↑
+ // discards the "second" field, only taking the "first"
+}
+```
+
+To completely ignore the rest of the fields, use `..` at the end of the list:
+
+```tact
+struct Many { one: Int; two: Int; three: Int; fans: Int }
+
+fun ignore(s: Many) {
+ let Many { fans, .. } = s;
+ // --
+ // ↑
+ // ignores all the unspecified fields,
+ // defining only "fans"
+}
+```
+
+:::caution
+
+ At the moment, destructuring of nested [Structs][s] or [Messages][m] isn't allowed. That is, the following won't work:
+
+ ```tact
+ struct First { nested: Second }
+ struct Second { field: Int }
+
+ fun example() {
+ let prep = First { nested: Second { field: 42 } };
+ let First { nested: { field: thing } } = prep;
+ // ^ this will result in the parse error:
+ // expected "_", "A".."Z", or "a".."z"
+ }
+ ```
+
+:::
+
## Branches
Control the flow of the code.
@@ -415,3 +568,5 @@ foreach (_, _ in quartiles) {
:::
[int]: /book/integers
+[s]: /book/structs-and-messages#structs
+[m]: /book/structs-and-messages#messages