Skip to main content
Every function declaration or definition follows a common pattern. The general form is:
[<forall declarator>] <return type> <function name>(<comma separated function args>) <specifiers> <function body>
where [ ... ] represents an optional entry. Here,

Return type

The return type can be any atomic or composite type, as described in the Types section. For example, the following functions are valid:
int foo() {...}
(int, int) foo'() {...}
[int, int] foo''() {...}
(int -> int) foo'''() {...}
() foo''''() {...}
FunC also supports type inference with the use of underscore _ as the return type. For example:
_ divAndMod(int m, int n) {
  return (m /% n);
}
The function divAndMod has the inferred type (int, int) -> (int, int). The function computes the division and modulo of the parameters m and n by using the division and modulo operator /%, which always returns a two-element tensor (int, int).

Function name

A function name can be any valid identifier. Additionally, it may start with the symbols . or ~, which have specific meanings explained in the special function call notation section. Specifically, refer to this section to understand how the symbols . or ~ affect the function name. For example, udict_add_builder?, dict_set, and ~dict_set are all valid function names, and each is distinct. These functions are defined in stdlib.fc. FunC reserves several function names. See the reserved functions article for more details.

Function arguments

A function can receive zero or more argument declarations, each declaration separated by a comma. The following kinds of argument declarations are allowed:
  • Ordinary declaration: an argument is declared using its type followed by its name. Example:
    int foo(int x) {
      return x + 2;
    }
    
    Here, int x declares an argument named x of type int in function foo. An example that declares multiple arguments:
    int foo(int x, int y) {
      return x + y;
    }
    
    An example that declares no arguments:
    int foo() {
      return 0;
    }
    
    
  • Unused argument declaration: only its type needs to be specified. Example:
    int first(int x, int) {
      return x;
    }
    
    This is a valid function of type (int, int) -> int, but the function does not use its second argument.
  • Argument with inferred type declaration: If an argument’s type is not explicitly declared, it is inferred by the type-checker. For example,
    int inc(x) {
      return x + 1;
    }
    
    This defines a function inc with the inferred type int -> int, meaning x is automatically recognized as an int.
Even though a function may appear to take multiple arguments, it takes a single tensor type argument. For more details on this distinction, refer to the function call section. However, for convenience, the individual components of this tensor are conventionally referred to as function arguments.

Specifiers

In FunC, function specifiers modify the behavior of functions. There are three types:
  1. impure
  2. Either inline or inline_ref, but not both
  3. method_id
One, multiple, or none can be used in a function declaration. However, they must appear in the order of the above list (e.g., impure must come before inline and method_id; inline_ref must come before method_id, etc).

impure specifier

The impure specifier indicates that a function has side effects, such as modifying contract storage, sending messages, or throwing exceptions. If a function is not marked as impure and its result is unused, the FunC compiler may delete the function call for optimization. For example, the stdlib.fc function random changes the internal state of the random number generator:
int random() impure asm "RANDU256";
The impure keyword prevents the compiler from removing calls to this function:
var n = 0;
random();     ;; Even though the result of random is not used,
              ;; the compiler will not remove this call 
              ;; because random has the impure specifier.

Inline specifier

A function marked as inline is directly substituted into the code wherever it is called, eliminating the function call overhead. Recursive calls are not allowed for inline functions. For example,
(int) add(int a, int b) inline {
    return a + b;
}
Since the add function is marked with the inline specifier, the compiler substitutes add(a, b) with a + b directly in the code. For instance, the compiler will replace the following code:
var a = 1;
var b = 2;
var n = add(a, b);   
with this code:
var a = 1;
var b = 2;
var n = a + b;

inline_ref specifier

When a function is marked with the inline_ref specifier, its code is stored in a separate cell. Each time the function is called, the TVM executes a CALLREF command. This works similarly to inline, but with a key difference: since the same cell can be reused multiple times without duplication, inline_ref is generally more efficient regarding code size. The only case where inline might be preferable is if the function is called just once. However, recursive calls to inline_ref functions remain impossible, as TVM cells do not support cyclic references.

method_id specifier

In a TVM program, every function has an internal integer ID that identifies it uniquely. These IDs are necessary because of the way the TVM calls functions within a program: it uses a dictionary where each key is a function ID that maps to the corresponding function code. When the TVM needs to invoke a particular function, the TVM looks up the ID in the dictionary and executes the corresponding code. By default, functions are assigned sequential numbers starting from 1. If a function has the method_id specifier, the compiler will compute an ID using the formula (crc16(<function_name>) & 0xffff) | 0x10000 instead. Additionally, such function becomes a get-method (or getter method), which are functions that can be invoked by its name in lite client or TON explorer. The method_id specifier has the variant method_id(<some_number>), which allows you to set a function’s ID to a specific number manually. For example, this defines a function whose ID is computed by the compiler and the function is available as a get-method in TON blockchain explorers:
int get_counter() method_id {
  load_data();
  return ctx_counter;  ;; Some global variable
}
This other example defines the same function, but this time it sets the specific ID 65536. Again, the function is available as a get-method in TON explorers.
int get_counter() method_id(65536) {
  load_data();
  return ctx_counter;  ;; Some global variable
}
Important limitations and recommendations19-bit limitation: Method IDs are limited to signed 19-bit integers, meaning the valid range is from -2^18 (inclusive) to (2^18 - 1) (inclusive), i.e., from -262,144 to 262,143.Reserved ranges:
  • 0-999: Reserved for system functions (approximate range).
  • Reserved functions: main/recv_internal (id=0), recv_external (id=-1), run_ticktock (id=-2), split_prepare (id=-3), split_install (id=-4)
  • 65536+: Default range for user functions when using automatic generation: (crc16(function_name) & 0xffff) | 0x10000
Best practice: It’s recommended to avoid setting method IDs manually and rely on automatic generation instead. Manual assignment can lead to conflicts and unexpected behavior.

Function body

Empty body

An empty body, marked with a single semicolon ; indicates that the function is declared but not yet defined. Its definition must appear later in the same file or a different file processed before the current one by the FunC compiler. A function with an empty body is also called a function declaration. For example:
   int add(int x, int y);
This declares a function named add with type (int, int) -> int but does not define it. In FunC, all functions must be defined or declared before using them in other functions, which explains the need for function declarations. For example, the following code calls function foo inside the main function, but foo is defined after main. Hence, the compiler rejects the code:
() main() {
  var a = foo();    ;; DOES NOT COMPILE
                    ;; foo is not declared nor 
                    ;; defined before main
}

int foo() {
  return 0;
}
To fix the error, either declare foo before main:
int foo();      ;; foo declared before main, 
                ;; but defined after main

() main() {
  var a = foo();
}

int foo() {
  return 0;
}
Or move the definition of foo before main:
int foo() {
  return 0;
}

() main() {
  var a = foo();
}

Assembler body

An assembler body defines the function using low-level TVM primitives for use in a FunC program. The body consists on the keyword asm, followed a list of TVM instructions, and ending with symbol ;. Refer to the assembler functions article for more details. For example:
  int add(int x, int y) asm "ADD";
This defines the function add of type (int, int) -> int, using the TVM instruction ADD.

Standard body

A standard body uses a block statement, i,e,. the body of the function is defined inside curly braces { }. For example:
int add(int x, int y) {
  return x + y;
}
This defines a function that adds its two arguments and returns the result of the addition.

Forall declarator

The forall declarator has the following syntax:
forall <comma separated list of type variables names> ->
The declarator starts with the forall keyword and finishes with the symbol ->. Each element in the comma separated list must be a type variable name. A type variable name can be any identifier, but capital letters are commonly used. The forall declarator makes the function a polymorphic function, meaning that when the function is called, the type variables get replaced with actual types. For example,
forall X, Y -> [Y, X] pair_swap([X, Y] pair) {
  [X p1, Y p2] = pair;
  return [p2, p1];
}
This function declares two type variables X and Y. The function uses these two type variables to declare an argument pair of type [X, Y], i.e., a tuple where the first component is of type X and the second component of type Y. The function then swaps the components of the tuple and returns a tuple of type [Y, X]. That pair_swap is polymorphic means that it can be called with tuples of type [int, int], [int, cell], [cell, slice], [[int, int], cell], etc. For instance:
  • pair_swap([2, 3]) returns [3, 2]. In this case, both type variables X and Y get substituted with int.
  • pair_swap([1, [2, 3, 4]]) returns [[2, 3, 4], 1]. In this case, type variable X gets substituted with int, and Y with [int, int, int].
Even though the function is polymorphic, the compiled assembly code remains the same for any substitution of the type variables. This is possible due to the polymorphic nature of stack manipulation operations. However, other forms of polymorphism, such as ad-hoc polymorphism with type classes, are not supported.
At the moment, type variables in polymorphic functions cannot be instantiated with tensor types. There is only one exception: the tensor type (a), where a is not a tensor type itself, since the compiler treats (a) as if it was a.This means you can’t use pair_swap on a tuple of type [(int, int), int] because type (int, int) is a tensor type.
I