---
title: Statements
description: "This page lists all the statements in Tact that can appear anywhere in function bodies."
---

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

The following statements can appear anywhere in a [function](/book/functions) body.

## `let` statement {#let}

The `let{:tact}` statement allows local and [block](#block)-scoped variable definitions. In Tact, variables are mutable, but **require** to be initialized with an expression.

However, type ascriptions can be omitted, and Tact will infer the type of the new variable from the computed value of the expression:

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

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

Note that the initial value of `null{:tact}` can mean either an empty [`map<K, V>{:tact}`](/book/maps) with arbitrary `K{:tact}` and `V{:tact}` types or the intentional absence of any other value for the [optional](/book/optionals) type. Therefore, whenever you declare a [`map<K, V>{:tact}`](/book/maps) or assign a [`null{:tact}`](/book/optionals) value, you must explicitly specify the type, as it cannot be inferred:

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

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

Naming a local variable with an underscore `_{:tact}` causes its value to be considered unused and discarded. This is useful if you do not need the return value of a function with side effects and want to explicitly mark the variable as unused. Note that such wildcard variable name `_{:tact}` cannot be accessed:

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

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

## `return` statement {#return}

The `return{:tact}` statement ends [function](/book/functions) execution and specifies a value to be returned to the [function](/book/functions) caller.

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

If the function does not have an explicit return type, it has an implicit return type of `void`. As such, the `return{:tact}` statement must be empty.

```tact
extends mutates fun equalize(self: Int, num: Int) {
    if (self == num) {
        return;
    } else {
        self = num;
        return;
    }
}
```

All statements after the `return{:tact}` statement are unreachable and will not be executed. Such statements are detected by the Tact compiler, producing an "Unreachable code" error.

This is done to help avoid potential logical errors in the code, although `return{:tact}`-reachability analysis is not almighty and might reject valid examples.

```tact
extends mutates fun equalize(self: Int, num: Int) {
    if (self == num) {
        return;
    } else {
        self = num;
        return;
    }

    throw(42); // COMPILATION ERROR! Unreachable statement
//  ~~~~~~~~~
}

fun retWhenNot(flag: Bool): Int {
    if (flag) {
        throw(200);
    } else {
        return 42;
    }

    return 1000; // COMPILATION ERROR! Unreachable statement
//  ~~~~~~~~~~~
}

fun throwWrapped(code: Int) {
    throw(code);
}

// The following function always throws, but this cannot be
// determined without a thorough inter-procedural analysis
// and, as such, compiler won't allow it.
//
// COMPILATION ERROR! Function does not always return a result
fun triggerCompiler(): Int {
//  ~~~~~~~~~~~~~~~
    throwWrapped(42);
}
```

## Block

A block statement is used to group zero or more statements. The block is delimited by a pair of braces ("curly braces", `{}{:tact}`) and contains a list of zero or more statements and declarations.

Some statements, such as [`let{:tact}`](#let) or [`return{:tact}`](#return), must end with a terminating semicolon `;{:tact}`. However, the semicolon of the last statement in the block is optional and may be omitted.

```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,
                // omitting the last and only semicolon

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

## Expression

An expression statement is an expression used in a place where a statement is expected. The expression is evaluated, and its result is discarded. Therefore, it makes sense only for expressions that have side effects, such as executing a function or updating a variable.

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

## Assignment

Assignment statements use an [assignment operator](/book/operators#assignment) (`={:tact}`) or [augmented assignment operators](/book/operators#augmented-assignment) (assignments combined with an operation):

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

:::note

  Read more about assignment and augmented assignment in their dedicated section: [assignment operators](/book/operators#assignment).

:::

## Destructuring assignment

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

The destructuring assignment is a concise way to unpack [structs][struct] and [Messages][message] into distinct variables. It mirrors the [instantiation syntax](/book/expressions#instantiation), but instead of creating a new structure 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 {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;
}
```

Just like in [instantiation](/book/expressions#instantiation), a 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 the 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 fields retain their values and types under their names regardless of the order in which they appear in their definition in the respective [struct][struct] or [Message][message].

```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 in 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 relevant 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][struct] or [Messages][message] 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: Second { field: thing } } = prep;
      //                         ^ this will result in the parse error:
      //                         expected "," or "}"
  }
  ```

:::

## Branches

Control the flow of the code.

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

:::caution

  Curly brackets (code blocks) are required!

:::

When executing an `if...else{:tact}` statement, first, the specified condition is evaluated. If the resulting value is `true{:tact}`, the following statement block is executed. Otherwise, if the condition evaluates to `false{:tact}`, the optional `else{:tact}` block is executed. If the `else{:tact}` block is missing, nothing happens, and execution continues further.

Regular `if{:tact}` statement:

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

With `else{:tact}` block:

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

With nested `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 also has a ternary expression `?:{:tact}`, which is described earlier in the Book: [Ternary](/book/operators#ternary).

:::

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

The `try...catch{:tact}` statement consists of a `try{:tact}` block and an optional `catch{:tact}` block, which receives an [`Int{:tact}`][int] [exit code](/book/exit-codes) as its only argument. The code in the `try{:tact}` block is executed first, and if it fails, the code in the `catch{:tact}` block will be executed, and changes made in the `try{:tact}` block will be rolled back, if possible.

:::note

  Note that some TVM state parameters, such as codepage and gas counters, will not be rolled back. That is, all gas usage in the `try{:tact}` block will be taken into account, and the effects of opcodes that change the gas limit will be preserved.

:::

Regular `try{:tact}` statement:

```tact
fun braveAndTrue() {
    // Let's 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);
}
```

With `catch (e){:tact}` block:

```tact
fun niceCatch() {
    // Let's 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
    }
}
```

With nested `try...catch{:tact}`:

```tact
try {
    // Preparing an x equal to 0, in such a way that the 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)
}
```

Note that similar to the [`let{:tact}` statement](#let), the captured [exit code](/book/exit-codes) in the `catch (){:tact}` clause can be discarded by specifying an underscore `_{:tact}` in its place:

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

:::note

  Read more about exit codes on the dedicated page: [Exit codes in the Book](/book/exit-codes).

:::

## Loops

Conditionally repeat certain blocks of code multiple times.

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

The `repeat{:tact}` loop executes a block of code a specified number of times. The number of repetitions should be given as a positive 32-bit [`Int{:tact}`][int] in the inclusive range from $1$ to $2^{31} - 1$. If the value is greater, an error with [exit code 5](/book/exit-codes#5), `Integer out of expected range`, will be thrown.

If the specified number of repetitions is equal to $0$ or any negative number in the inclusive range from $-2^{256}$ to $-1$, it is ignored, and the code block is not executed at all.

```tact
let twoPow: Int = 1;

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

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

twoPow; // 1024
```

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

The `while{:tact}` loop continues executing the block of code as long as the given condition is `true{:tact}`.

In the following example, the value of `x` is decremented by 1 on each iteration, so the loop will run 10 times:

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

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

The `do...until{:tact}` loop is a post-test loop that executes the block of code at least once and then continues to execute it until the given condition becomes `true{:tact}`.

In the following example, the value of `x` is decremented by 1 on each iteration, so the loop will run 10 times:

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

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

The `foreach{:tact}` loop operates on key-value pairs (entries) of the [`map<K, V>{:tact}`](/book/maps) type in sequential order: from the smallest keys of the map to the biggest ones.

This loop executes a block of code for each entry in the given map, capturing the key and value on each iteration. This is handy when you don't know in advance how many items there are in the map or don't want to explicitly look for each of the entries using the [`.get(){:tact}`](/book/maps#get) [method](/book/functions#extensions) of maps.

Note that the names of captured keys and values in each iteration are arbitrary and can be any valid Tact identifier, provided they are new to the current scope. The most common options are: `k` and `v`, or `key` and `value`.

In the following example, the map `cells` has $4$ entries, so the loop will run $4$ times:

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

It's also possible to iterate over a map in contract storage, and over maps as members of instances of [structure](/book/structs-and-messages) types:

```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) {
            // ...
        }
    }
}
```

Similar to the [`let{:tact}` statement](#let), either of the captured key or value (or both) can be discarded by specifying an underscore `_{:tact}` in their place:

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

  At the moment, `foreach{:tact}` works only with explicitly provided map identifiers and nested identifier constructions, like `foo.bar.targetMap{:tact}` or `self.baz.targetMap{:tact}`. That is, returning a map from a function and trying to iterate over its entries won't work:

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

  Trying to iterate over a map member of a [struct][struct] returned from a function also won't work, because the function call is an expression and neither an identifier nor a nested identifier access:

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

  For additional loop examples, see: [Loops in Tact-By-Example](https://tact-by-example.org/04-loops).

:::

[int]: /book/integers
[struct]: /book/structs-and-messages#structs
[message]: /book/structs-and-messages#messages
