Skip to main content
FunC statements can occur anywhere inside function bodies.

Expression statements

The most common type of statement is the expression statement—an expression followed by ;. See the FunC expressions article for details on the allowed expressions.

return statement

The return statement ends function execution and specifies a value to be returned to the function caller. Any statement after the return statement is not executed. In this example, the return statement instructs the function to halt execution and produce x + 1 as a result.
int inc(int x) {
  return x + 1;
}
In this example, only the first return executes:
int inc(int x) {
  return x + 1;
  return x;
}

Block statement

A block statement is used to group zero or more statements. The block is delimited by a pair of curly braces { ... }. A block statement also defines a scope, in which variables defined in the block are accessible only in the block or in nested blocks. For example:
int x = 1;
int y = 2;
{ ;; x and y are accessible in this block.
  int z = x + y;
  {
    ;; x, y, and z are accessible in this block.
    int w = z;    ;; The block declares w, 
                  ;; which is only accessible in this block.
  }
  ;; w is no longer accessible here.
  ;; x, y, and z are still accessible here.
}
;; z is no longer accessible here, but x and y are.

Conditional statements

These statements control the flow of the code based on a condition.

if...else statement

When executing an if...else statement, first, the specified condition is evaluated. If the condition evaluates to an integer different from 0 (see absence of boolean type), the block after the condition is executed. Otherwise, if the condition evaluates to 0, the optional else block is executed. If the else block is missing, nothing happens, and execution continues further. Examples:
if (1 < 10) {   ;; The condition evaluates to -1, so the block executes
  do_something();
}
if (11 < 10) {   ;; The condition evaluates to 0, so the block does not execute
  do_something();
} 
;; No else is provided. So, execution continues here, after the block.
if (11 < 10) {   ;; The condition evaluates to 0, so the block does not execute
  do_something();
} else {         ;; else is provided. So, the else block executes.
  handle_else();
} 
Curly brackets {} are required in each block of an if...else statement. For example, the following code will not compile:
if (1 < 10) {
  do_something();
} else if (2 > 1) {   ;; else block must have curly brackets
  do_something2();
}
because the else block must have curly brackets:
if (1 < 10) {
  do_something();
} else {    ;; else block now has curly brackets
  if (2 > 1) { 
    do_something2();
  }
}
The above example can be written in a simpler form by using the elseif keyword, to avoid the need to write several nested curly brackets in the else case:
if (1 < 10) {
  do_something();
} elseif (2 > 1) { 
  do_something2();
}
In general, the elseif keyword is useful for stating several alternative cases:
if (cond) {
  do_1();
} elseif (cond2) {
  do_2();
} elseif (cond23) {
  do_3();
} else {
  do_4();
}
The alternative cases can also include the elseifnot keyword, which allows the inclusion of ifnot statements in the alternatives:
if (cond) {
  do_1();
} elseif (cond2) {  ;; if in else case
  do_2();
} elseifnot (cond23) {  ;; ifnot in else case
  do_3();
} else {
  do_4();
}

ifnot...else statement

The ifnot...else statement is equivalent to the if...else statement but with the condition negated using the bitwise ~ operator. More specifically:
ifnot (cond) {
  do_something();
} else {
  handle_else();
}
is equivalent to:
if (~ cond) {  ;; Standard if..else, with condition negated using ~
  do_something();
} else {
  handle_else();
}
In other words, if the condition in the ifnot evaluates to 0 (see absence of boolean type), the block after the condition is executed. Otherwise, if the condition evaluates to an integer different from 0, the optional else block is executed. If the else block is missing, nothing happens, and execution continues further. Examples:
ifnot (1 > 10) {   ;; The condition evaluates to 0, the block executes
  do_something();
}
ifnot (11 > 10) {   ;; The condition evaluates to -1, the block does not execute
  do_something();
} 
;; No else is provided. Execution continues here, after the block.
ifnot (11 > 10) {   ;; The condition evaluates to -1, the block does not execute
  do_something();
} else {         ;; else is provided. The else block executes.
  handle_else();
} 
Similarly to the if...else, it is possible to use the keyword elseifnot to add several alternative cases:
ifnot (cond) {
  do_1();
} elseifnot (cond2) {
  do_2();
} elseifnot (cond23) {
  do_3();
} else {
  do_4();
}
The alternative cases can also include the elseif keyword, which allows the inclusion of standard if statements in the alternatives:
ifnot (cond) {      ;; ifnot
  do_1();
} elseif (cond2) {  ;; if in else case
  do_2();
} elseifnot (cond23) {  ;; ifnot in else case
  do_3();
} else {
  do_4();
}

Loops

FunC supports repeat, while, and do { ... } until loops. The for loop is not supported.

repeat loop

The repeat loop executes a block of code a specified number of times. The number of repetitions should be given as a positive 32-bit integer in the inclusive range from 1 to 2^31 - 1 (i.e., 2,147,483,647). If the value is greater, an error with exit code 5, Integer out of expected range, will be thrown.
int x = 1;
repeat(10) { ;; Repeats the block 10 times.
  x *= 2;    ;; Each iteration multiplies x by 2.
}
;; x has value 1024
int x = 1;
int y = 10;
repeat(y + 6) {  ;; Repeats the block 16 times.
  x *= 2;        ;; Each iteration multiplies x by 2.
}
;; x has value 65536
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.
int x = 1;
repeat(-1) {  ;; Block does not execute.
  x *= 2;
}
;; x has value 1

while loop

The while loop continues executing the block of code as long as the given condition evaluates to an integer different from 0. See absence of boolean type.
int x = 5; 
while (x < 10) {  ;; Executes the block 5 times.
                  ;; Each iteration increases x by 1.
                  ;; The loop stops when x becomes 10.
  x += 1;
}
;; x has value 10

do...until loop

The do...until 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 evaluates to an integer different from 0. See absence of boolean type.
int x = 0;
do {      ;; The block always executes at least once
  x += 3;
} until (x % 9 == 0);  ;; Executes the block 3 times.
                       ;; Each iteration increases x by 3.
                       ;; The loop stops when x becomes divisible by 9.
;; x has value 9

try...catch statement

Available in FunC since v0.4.0 The try...catch statement consists of a try block and a catch block. The code in the try block is executed first, and if it fails, all changes made within the try block are rolled back, and the catch block is executed instead. The catch block has two arguments, which are local to the catch block:
  • The exception parameter, which can be of any type. Used to provide extra information about the error.
  • The error code, an integer, which identifies the kind of error.
try {
  do_something();
} catch (x, n) {
  ;; x is the exception parameter 
  ;; n is the error code
  handle_exception();
}
Unlike many other languages, in FunC, all changes are undone if an error occurs inside the try block. These modifications include updates to local and global variables and changes to control registers (for example, c4 for storage, c5for action/messages, c7 for context, etc.). Any contract storage updates and outgoing messages are also reverted. However, certain TVM state components are not rolled back, such as:
  • Codepage settings
  • Gas counters
I am not sure if “Codepage settings” and “gas counters” refer to some TVM state component in page ton/tvm#1-4-total-state-of-tvm-scccg
As a result, all gas consumed within the try block is still accounted for, and any modifications carried out by operations that change gas limits (e.g., accept_message or set_gas_limit) will remain in effect. In this example,
int x = 0;
try {
  x += 1;     ;; x now has value 1
  throw(100);
} catch (arg, e) {
  ;; x is rolled back to value 0.
  x += 2;
}
;; Here, x has value 2.
although x is incremented to 1 inside the try block, the modification is rolled back due to the exception produced by the throw function. Hence, x has value 0 at the moment the catch block starts execution. However, the gas consumed inside the try block is not rolled back when the catch block starts execution. Here is an example which illustrates how to generate and use the exception parameter:
int x = 10;
try {
  throw_arg(-1, 100);    ;; throw an exception with error code 100 
                         ;; and exception parameter -1
} catch (arg, e) {
  if (e == 100) {        ;; Handle exceptions with error code 100
     arg.cast_to_int();  ;; Tell the type checker that the 
                         ;; exception parameter is an integer
                         ;; arg = -1, e = 100
     x = arg + 1;
  }
}
;; x has value 0
In the above example, throw_arg(-1, 100) produces an exception with error code 100 and exception parameter -1. However, since the exception parameter can be of any type, which may vary depending on the exception, FunC cannot determine its type at compile time in the catch block. This requires the developer to manually cast the exception parameter. In the example, casting of the exception parameter is achieved with the assembler and polymorphic function cast_to_int, which receives an argument of any type and returns the same argument as an integer by wrapping the NOP (no operation) TVM instruction:
forall X -> int cast_to_int(X x) asm "NOP";
I