-
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. For example:This declares a function namedaddwith the type(int, int) → intbut does not define it. -
An assembler function body definition defines the function using low-level TVM primitives for use in a FunC program. For example:
This defines the function
addusing the TVM opcodeADD, keeping its type as(int, int) → int. -
A standard function body uses a block statement, the most common way to define functions. For example:
This is a standard definition of the
addfunction.
Function declaration
As mentioned earlier, every function declaration or definition follows a common pattern. The general form is:[ ... ] represents an optional entry.
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 Statements section.
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.
Special function names
FunC (specifically, the Fift assembler) reserves several function names with predefined IDs:mainandrecv_internalhaveid = 0recv_externalhasid = -1run_ticktockhasid = -2
id = 0, meaning it must define either main or recv_internal.The run_ticktock function is used in ticktock transactions of special smart contracts.
Receive internal
Therecv_internal function is invoked when a smart contract receives an inbound internal message. When the TVM initializes, certain variables are automatically placed on the stack. By specifying arguments in recv_internal, the smart contract can access some of these values. Any values not explicitly referenced in the function parameters will remain unused at the bottom of the stack.
The following recv_internal function declarations are all valid. Functions with fewer parameters consume slightly less gas, as each unused argument results in an additional DROP instruction:
Receive external
Therecv_external function handles inbound external messages.
Return type
The return type can be any atomic or composite type, as described in the Types section. For example, the following function declarations are valid:pyth, which has the inferred type (int, int) → (int, int, int).
It computes Pythagorean triples based on the given input values.
Function arguments
In function arguments, commas separate it. The following types of argument declarations are valid:-
Ordinary declaration: an argument is declared using its type followed by its name. Example:
int xdeclares an argument namedxof typeintin the function declaration:() foo(int x);. -
Unused argument declaration: only its type needs to be specified. Example:
This is a valid function definition of type
(int, int) → int. -
Argument with inferred type declaration: If an argument’s type is not explicitly declared, it is inferred by the type-checker.
For example,
This defines a function
incwith the inferred typeint → int, meaningxis automatically recognized as anint.
Function calls
Non-modifying methods
A non-modifying function supports a shorthand method call syntax using
.store_uint has the type (builder, int, int) → builder, where:
- The second argument is the value to store.
- The third argument is the bit length.
begin_cell creates a new builder. The following two code snippets are equivalent:
.. The code can be further simplified:
The function’s first argument is passed before the function name, separated by .. The syntax can be further condensed into a single statement:
Modifying functions
A modifying function supports a short form using the
~ and . operators.- The first argument of a function has type
A. - The function’s return type is
(A, B), whereBis any arbitrary type.
load_uint
Suppose cs is a cell slice, and load_uint has type (slice, int) → (slice, int). It means:
load_uinttakes a cell slice and several bits to load.- It returns the remaining slice and the loaded value.
inc of type int → int, which increments an integer. To use it as a modifying method, we define it as follows:
x in place.
. and ~ in function names
Suppose we want to use inc as a non-modifying method. We can write:
inc as a modifying method:
- If a function is called with
.(e.g.,x.foo()), the compiler looks for a.foodefinition. - If a function is called with
~(e.g.,x~foo()), the compiler looks for a~foodefinition. - If neither
.foonor~foois defined, the compiler falls back to the regularfoodefinition.
Specifiers
In FunC, function specifiers modify the behavior of functions. There are three types:impureinline/inline_refmethod_id
impure must come before inline).
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, in the stdlib.fc function:
RANDU256 changes the internal state of the random number generator. The impure keyword prevents the compiler from removing this function call.
inline specifier
A function marked as inline is directly substituted into the code wherever it is called.
Recursive calls are not allowed for inline functions.
Example
add function is marked with the inline specifier, the compiler substitutes add(a, b) with a + b directly in the code, eliminating the function call overhead.
Another example of using inline from ICO-Minter.fc:
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, 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
In a TVM program, every function has an internal integer ID that determines how it can be called.
By default, ordinary functions are assigned sequential numbers starting from 1, while contract get-methods use crc16 hashes of their names.
The method_id(<some_number>) specifier allows you to set a function’s ID to a specific value manually.
If no ID is specified, the default is calculated as (crc16(<function_name>) & 0xffff) | 0x10000.
If a function has the method_id specifier, it can be invoked by its name as a get-method in lite client or TON explorer.
Important limitations and recommendations19-bit limitation: Method IDs are limited to 19 bits by the TVM assembler, meaning the valid range is 0 to 524,287 (2^19 - 1).Reserved ranges:
- 0-999: Reserved for system functions (approximate range)
- Special functions:
main/recv_internal(id=0),recv_external(id=-1),run_ticktock(id=-2) - 65536+: Default range for user functions when using automatic generation
(crc16() & 0xffff) | 0x10000
Polymorphism with forall
Before any function declaration or definition, there can beforall type variables declarator. It has the following syntax:
A function definition can include a forall type variable declaration before its declaration or implementation. The syntax is:
pair_swap([2, 3])returns[3, 2];pair_swap([1, [2, 3, 4]])returns[[2, 3, 4], 1].
X and Y are type variables. When the function is called, these variables are replaced with actual types, and the function executes accordingly. Even though the function is polymorphic, the compiled assembly code remains the same for any type substitution. 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 currently supported.
It is important to note that X and Y must each have a type width of 1, meaning they should fit within a single stack entry. This means you can’t use pair_swap on a tuple like [(int, int), int] because type (int, int) has a width of 2, taking up two stack entries instead of one.
Assembler function body definition
In FunC, functions can be defined directly using assembler code. This is done using theasm keyword, followed by one or more assembler commands written as strings.
For example, the following function increments an integer and then negates it:
INC and NEGATE. An alternative way to define the function is:
When called, this function is directly translated into the two assembler commands, INC and NEGATE.
Alternatively, the function can be written as:
INC NEGATE is treated as a single assembler command by FunC, but the Fift assembler correctly interprets it as two separate commands.
The list of assembler commands can be found here: TVM instructions.
Rearranging stack entries
Sometimes, the order in which function arguments are passed may not match the expected order of an assembler command. Similarly, the returned values may need to be arranged differently. While this can be done manually using stack manipulation primitives, FunC automatically handles it.When manually rearranging arguments, they are computed in the new order. To overwrite this behavior use
#pragma compute-asm-ltr.STUXQ takes an integer, a builder, and another integer as input. It then returns the builder and an integer flag indicating whether the operation succeeded. We can define the corresponding function as follows:
asm declaration:
asm keyword.
This allows us to control the order in which arguments are passed to the assembler command.
Similarly, we can rearrange return values using the following notation:
0 represents the deepest stack entry.
Additionally, we can combine these techniques:
Multi-line asms
Multi-line assembler commands, including Fift code snippets, can be defined using triple-quoted strings""".