---
title: 语句
description: 此页面列出所有Tact语句，这些语句可以显示在函数的任何位置
---

import { Badge } from '@astrojs/starlight/components';

以下语句可出现在 [function](/zh-cn/book/functions) 主体的任何位置。

## `let`语句 {#let}

`let{:tact}` 语句允许声明局部变量和 [block](#block)-scoped 变量。

在Tact中，声明一个局部变量总是需要一个初始值。 但是，也可以省略类型标注，Tact 会尝试从初始值推断类型：

```tact
let value: Int = 123; // full definition with type and value
let vInferred = 123;  // inferred type Int

let vExplicitCtx: Context = context(); // explicit type Context, a built-in Struct
let vCtx = context();                  // inferred type Context
```

请注意，`null{:tact}`的初始值既可以指具有任意 `K{:tact}`和 `V{:tact}`类型的空[`map<K, V>{:tact}`](/zh-cn/book/maps)，也可以指故意不为[optional](/zh-cn/book/optionals)类型设置任何其他值。  这就是为什么在声明 [optional](/zh-cn/book/optionals) 或 [`map<K, V>{:tact}`](/zh-cn/book/maps)时，需要明确指定类型，因为无法推断：

```tact
let vOptional: Int? = null; // explicit type Int or null
let vOptInt = 42;           // implicit type Int
vOptInt = null;             // COMPILATION ERROR!

let vMap: map<Int, Int> = emptyMap(); // explicit type map<Int, Int>
let vMapWithSerialization: map<Int as uint8, Int as uint8> = emptyMap();
```

用下划线 `_{:tact}` 命名局部变量时，其值将被视为未使用并丢弃。 当你不需要某个函数的返回值（有副作用），并想明确地将变量标记为未使用时，这种方法就很有用。 注意，不能访问通配符变量名 `_{:tact}`： 当你不需要某个函数的返回值（有副作用），并想明确地将变量标记为未使用时，这种方法就很有用。 注意，不能访问通配符变量名 `_{:tact}`：

```tact
let _ = someFunctionWithSideEffects(); // with type inference
let _: map<Int, Int> = emptyMap();     // with explicit type

dump(_); // COMPILATION ERROR! Cannot access _
```

## `return` 语句 {#return}

`return{:tact}` 语句结束 [function](/zh-cn/book/functions) 的执行，并指定要返回给 [function](/zh-cn/book/functions) 调用者的值。

```tact
// Simple wrapper over stdlib function now()
fun getTimeFromNow(offset: Int): Int {
    return now() + offset;
}
```

## Block

块语句用于组合零个或多个语句。 块语句用于组合零个或多个语句。 块由一对大括号（"大括号"、`{}{:tact}`）分隔，包含一个由零个或多个语句和声明组成的列表。

某些语句，如 [`let{:tact}`](#let) 或 [`return{:tact}`](#return)，必须以结束分号 `;{:tact}` 结束。  不过，语块中最后一条语句的分号是可选的，可以省略。

```tact
{ // <- start of the block
    // arbitrary statements:
    let value: Int = 2 + 2;
    dump(value);
} // <- end of the block

{ dump(2 + 2) } // a block with only one statement,
                // omitted the last and only semicolon

{
    let nah = 3 * 3 * 3; // a block with two statements,
    let yay = nah + 42   // but without the last semicolon
}
```

## 表达式

表达式语句是一种表达式，用于预期需要语句的地方。  表达式被求值后，其结果将被丢弃--因此，它只适用于有副作用的表达式，如执行函数或更新变量。

```tact
dump(2 + 2); // stdlib function
```

## 赋值

赋值语句使用 [赋值运算符](/zh-cn/book/operators#assignment) (`={:tact}`)或 [增强赋值运算符](/zh-cn/book/operators#augmented-assignment) (赋值与运算相结合)：

```tact
let value: Int = 0; // definition
value = 5;          // assignment
value += 5;         // augmented assignment (one of the many, see below)
```

:::note

  有关赋值和增强赋值的更多信息，请参阅其专门章节：[赋值运算符](/zh-cn/book/operators#assignment)。

:::

## 解构赋值

<Badge text="Available since Tact 1.6" variant="tip" size="medium"/><p/>

解构赋值是一种简洁的方法，可以将[结构][s]和[消息][m]解包为不同的变量。 它镜像了[实例化语法](/zh-cn/book/expressions#instantiation)，但不是创建新的[结构体][s]或[消息][m]，而是将每个字段或部分字段绑定到它们各自的变量。

该语法源于 [`let`语句](#let)，它不是直接指定变量名，而是在[赋值运算符 `={:tact}`](/zh-cn/book/operators#assignment)的左侧指定结构类型，与右侧值的结构类型相对应。

```tact {9}
// 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;
}
```

就像在[实例化](/zh-cn/book/expressions#instantiation)中一样，允许使用尾随逗号。

```tact
struct Example { number: Int }

fun trailblazing() {
    let Example {
        number,     // trailing comma inside variable list
    } = Example {
        number: 42, // trailing comma inside field list
    };
}
```

:::note

  [增强赋值运算符](/zh-cn/book/operators#augmented-assignment) 对此类赋值没有意义，因此会被报告为解析错误：

```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 "="
}
```

:::

要创建一个不同变量名下的绑定，请在分号`:{: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
}
```

请注意，绑定的顺序无关紧要——所有字段都保留其名称下的值和类型，不管它们在各自的 [Struct][s] 或 [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
}
```

解构赋值是彻底的，并且需要将所有字段指定为变量。 要故意忽略某些字段，可以使用下划线 `_{:tact}`，这将丢弃被考虑的字段的值。 注意，这些通配符变量名 `_{:tact}` 无法访问：

```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"
}
```

若要完全忽略其余字段，请在列表末尾使用 `..` ：

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

  目前，不允许对嵌套的[结构体][s]或[消息][m]进行解构。 也就是说，以下将不工作:

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

控制代码流

### `if...else` {#if-else}

:::caution

  需要使用大括号（代码块）！

:::

在执行 `if...else{:tact}` 语句时，首先会对指定条件进行评估。 如果结果值为 `true{:tact}`，则执行下面的语句块。  否则，如果条件评估结果为 `false{:tact}`，将执行可选的 `else{:tact}` 块。 如果缺少 `else{:tact}` 块，则什么也不会发生，执行仍将继续。

常规 `if{:tact}` 语句：

```tact
// condition
// ↓
if (true) { // consequence, when condition is true
    dump(2 + 2);
}
```

`else{:tact}` 块：

```tact
// condition
// ↓
if (2 + 2 == 4) {
    // consequence, when condition is true
    dump(true);
} else {
    // alternative, when condition is false
    dump(false);
}
```

使用嵌套的 `if...else{:tact}`：

```tact
// condition
// ↓
if (2 + 2 == 3) {
    // consequence, when condition is true
    dump("3?");
//        condition2
//        ↓
} else if (2 + 2 == 4) {
    // another consequence, when condition2 is true
    dump(true);
} else {
    // alternative, when both condition and condition2 are false
    dump(false);
}
```

:::note

  Tact 也有一个三元表达式 `?:{:tact}`，在本书前面已有介绍：[三元表达式](/zh-cn/book/operators#ternary)。

:::

### `try...catch` {#try-catch}

`try...catch{:tact}`语句由一个 `try{:tact}`块和一个可选的 `catch{:tact}`块组成，它接收一个 [`Int{:tact}`][int][退出码](/zh-cn/book/exit-codes)作为唯一参数。 首先执行 `try{:tact}` 块中的代码，如果失败，则执行 `catch{:tact}` 块中的代码，并尽可能回滚 `try{:tact}` 块中的更改。

:::note

  请注意，某些 TVM 状态参数（如编码页和 gas 计数器）不会回滚。 也就是说，`try{:tact}`程序块中的所有 gas 使用量都将被考虑在内，而改变 gas 限值的操作码的效果也将被保留。 也就是说，`try{:tact}` 程序块中的所有 gas 使用量都将被考虑在内，而改变 gas 限值的操作码的效果也将被保留。

:::

常规 `try{:tact}` 语句：

```tact
fun braveAndTrue() {
    // Lets try and do something erroneous
    try {
        throw(1042); // throwing with exit code 1042
    }

    // The following will be executed as the erroneous code above was wrapped in a try block
    dump(1042);
}
```

用 `catch (e){:tact}` 块：

```tact
fun niceCatch() {
    // Lets try and do something erroneous
    try {
        throw(1042); // throwing with exit code 1042
    } catch (err) {
        dump(err);       // this will dump the exit code caught, which is 1042
    }
}
```

使用嵌套的 `try...catch{:tact}`：

```tact
try {
    // Preparing an x equal to 0, in such a way that Tact compiler won't realize it (yet!)
    let xs: Slice = beginCell().storeUint(0, 1).endCell().beginParse();
    let x: Int = xs.loadUint(1); // 0

    try {
        throw(101);     // 1. throws with exit code 101
    } catch (err) {     // 2. catches the error and captures its exit code (101) as err
        return err / x; // 3. divides err by x, which is zero, throwing with exit code 4
    }

} catch (err) {         // 4. catches the new error and captures its exit code (4) as err
    //   ^^^ this works without name collisions because the previous err
    //       has a different scope and is only visible inside the previous catch block

    dump(err);          // 5. dumps the last caught exit code (4)
}
```

请注意，与 [`let{:tact}` 语句](#let)类似，在 `catch (){:tact}` 子句中捕获的[退出码](/zh-cn/book/exit-codes)可以通过指定下划线 `_{:tact}` 来丢弃：

```tact
try {
    throw(42);
} catch (_) {
    dump("I don't know the exit code anymore");
}
```

:::note

  在专用页面上阅读更多有关退出码的信息：[本书中的退出码](/zh-cn/book/exit-codes)。

:::

## 循环

有条件地多次重复某些代码块。

### `repeat` {#repeat-loop}

`repeat{:tact}` 循环执行一段代码指定的次数。 重复次数应以正 $32$-bit [`Int{:tact}`][int]的形式给出，范围从 $1$ 到 2^{31} - 1$。 如果数值更大，则会出现[退出码 5](/zh-cn/book/exit-codes#5)，"整数超出预期范围 "的错误。

如果指定的重复次数等于 $0$ 或包含范围 $-2^{256}$ 至 $-1$ 中的任何负数，则忽略该值，不执行代码块。

```tact
let twoPow: Int = 1;

// Repeat exactly 10 times
repeat (10) {
    twoPow *= 2;
}

// Skipped
repeat (-1) {
    twoPow *= 3333;
}

twoPow; // 1024
```

### `while` {#while-loop}

只要给定条件为 `true{:tact}`，`while{:tact}` 循环就会继续执行代码块。

在下面的示例中，每次迭代时，`x` 的值都会递减 $1$，因此循环将运行 $10$ 次：

```tact
let x: Int = 10;
while (x > 0) {
    x -= 1;
}
```

### `do...until` {#do-until-loop}

`do...until{:tact}`循环是一个后测试循环，它至少执行一次代码块，然后继续执行，直到给定条件变为 `true{:tact}`。

在下面的示例中，每次迭代时，`x` 的值都会递减 $1$，因此循环将运行 $10$ 次：

```tact
let x: Int = 10;
do {
    x -= 1;  // executes this code block at least once
} until (x <= 0);
```

### `foreach` {#foreach-loop}

`foreach{:tact}` 循环按顺序对 [`map<K, V>{:tact}`](/zh-cn/book/maps) 类型的键值对（条目）进行操作：从 map 的最小键到最大键。

该循环为给定映射中的每个条目执行一个代码块，每次迭代都会捕获键和值。 该循环为给定映射中的每个条目执行一个代码块，每次迭代都会捕获键和值。 当您事先不知道map中有多少个条目，或不想明确地使用map的 [`.get(){:tact}`](/zh-cn/book/maps#get) [method](/zh-cn/book/functions#extension-function) 查找每个条目时，这将非常方便。

请注意，每次迭代时捕获的键和值对的名称是任意的，可以是任何有效的 Tact 标识符，只要它们是当前作用域的新标识符即可。  最常见的选项是：`k` 和 `v`，或者 `key` 和 `value`。

在下面的示例中，地图 `cells` 有 $4$ 个条目，因此循环将运行 $4$ 次：

```tact
// Empty map
let cells: map<Int, Cell> = emptyMap();

// Setting four entries
cells.set(1, beginCell().storeUint(100, 16).endCell());
cells.set(2, beginCell().storeUint(200, 16).endCell());
cells.set(3, beginCell().storeUint(300, 16).endCell());
cells.set(4, beginCell().storeUint(400, 16).endCell());

// A variable for summing up the values
let sum: Int = 0;

// For each key and value pair in cells map, do:
foreach (key, value in cells) { // or just k, v
    let s: Slice = value.beginParse(); // convert Cell to Slice
    sum += s.loadUint(16);             // sum the Slice values
}
dump(sum); // 1000
```

还可以遍历合约存储中的映射，以及作为 [Struct](/zh-cn/book/structs-and-messages#structs) 或 [Message](/zh-cn/book/structs-and-messages#messages) 类型实例成员的映射：

```tact
import "@stdlib/deploy";

struct Fizz { oh_my: map<Int, Int> }
message Buzz { oh_my: map<Int, Int> }

contract Iterated {
    oh_my: map<Int, Int>;

    receive("call to iterate!") {
        let oh_my: map<Int, Int> = emptyMap();
        oh_my.set(0, 42);
        oh_my.set(1, 27);

        self.oh_my = oh_my; // assigning local map to the storage one
        let fizz = Fizz{ oh_my }; // field punning
        let buzz = Buzz{ oh_my }; // field punning

        // Iterating over map in contract storage
        foreach (key, value in self.oh_my) {
            // ...
        }

        // Iterating over map member of a Struct Fizz instance
        foreach (key, value in fizz.oh_my) {
            // ...
        }

        // Iterating over map member of a Message Buzz instance
        foreach (key, value in buzz.oh_my) {
            // ...
        }
    }
}
```

请注意，与 [`let{:tact}` 语句](#let)类似，可以通过指定下划线 `_{:tact}` 来丢弃捕获的键或值（或两者）：

```tact
// Empty map
let quartiles: map<Int, Int> = emptyMap();

// Setting some entries
quartiles.set(1, 25);
quartiles.set(2, 50);
quartiles.set(3, 75);

// Discarding captured keys
// without modifying them in the map itself
foreach (_, value in quartiles) {}

// Discarding captured values
// without modifying them in the map itself
foreach (key, _ in quartiles) {}

// Discarding both keys and values
// without modifying them in the map itself
foreach (_, _ in quartiles) {
    // Can't access via _, but can do desired operations
    // n times, where n is the current length of the map
}
```

:::caution

  请注意，目前 `foreach{:tact}` 只适用于明确提供的映射标识符和嵌套标识符结构，如 `foo.bar.targetMap{:tact}` 或 `self.baz.targetMap{:tact}`。  也就是说，从函数中返回一个映射并试图遍历其条目是行不通的：

```tact
foreach (k, v in emptyMap()) {
//               ^ this will give the following error message:
//                 foreach is only allowed over maps that are path expressions,
//                 i.e. identifiers, or sequences of direct contract/struct/message accesses,
//                 like "self.foo" or "self.structure.field"
}
```

  试图遍历函数返回的 [Struct](/zh-cn/book/structs-and-messages#structs)的 map 成员也是行不通的，因为函数调用是一种表达式，而不是标识符或嵌套标识符访问：

```tact
foreach (k, v in genCoolStruct().map) {
//               ^ this will give the following error message:
//                 foreach is only allowed over maps that are path expressions,
//                 i.e. identifiers, or sequences of direct contract/struct/message accesses,
//                 like "self.foo" or "self.structure.field"
}
```

:::

:::note

  有关其他循环示例，请参阅[循环示例](https://tact-by-example.org/04-loops)。

:::

[int]: /zh-cn/book/integers
[s]: /zh-cn/book/structs-and-messages#structs
[m]: /zh-cn/book/structs-and-messages#messages
