Skip to main content

Function declaration

A function is declared using the fun keyword, followed by the function name and parameters:

Parameter type

Each function parameter must have an explicit type.

Return type

A function may declare its return type explicitly:
If the return type is omitted, the compiler infers it from return statements:
If different return statements produce different types, the compiler reports an error. Union types are not inferred, as this usually indicates a bug.

Default parameter value

Default values are placed after the type and must be constant expressions:

Optional void parameters

If a trailing parameter has type void, it may be omitted at the call site. This is especially convenient for generic helpers where “no argument” is naturally expressed as void, instead of using a nullable wrapper or a pile of overloads.
The same convention has long been allowed for void-typed struct fields.

Method declaration

Methods are declared as extension functions with a receiver specified before the name:

Instance and static methods

If the first parameter is self, it is an instance method: fun <receiver>.f(self, ...). Otherwise, it is a static method: fun <receiver>.f(...).
Instance methods are invoked using obj.method(). Static methods are invoked on the receiver type.

Receiver and self

The type of self is determined by the receiver. All parameters after self must have explicit types. If the return type is omitted, it is inferred.

Methods for non-struct types

Methods are not limited to structures. They may be declared for any receiver type, unions, aliases, and primitives:
All standard methods are declared this way: fun cell.hash(self).

Immutability of self

In instance methods, self is immutable by default. To allow modifications, declare mutate self explicitly.

Returning self and method chaining

Returning self makes a method chainable. To enable chaining, declare the return type as self. For example, all methods of builder return self, which allows consecutive calls like b.storeXXX().storeXXX().

Generic functions

A function declared with type parameters T is called a generic function.

Type parameter inference

When calling a generic function, the compiler automatically infers type arguments:

Explicit type arguments

Type arguments may be specified explicitly using f<...>(args):

Multiple type parameters

A generic function may declare multiple type parameters:

Default type parameters

Generic functions may define default type parameters:
Default type parameters cannot reference other type parameters.

Generic functions as values

Since Tolk supports first-class functions, generic functions can be passed as arguments and stored in variables.

Assigning generic functions to variables

A generic function may be assigned to a variable. Since this is not a function call, <T> must be specified explicitly:

Generic methods

Declaring a method for a generic type does not differ from declaring any other method. When parsing the receiver, the compiler treats unknown symbols as type parameters.

Instance generic methods

Generic methods can be instance methods by declaring self:

Generic methods as values

Generic methods can be also used as first-class functions:

Methods for generic structure

Methods for generic structures can themselves be generic:

Specialization and overloading

Overloading and partial specialization allow declaring multiple methods with the same name for different receiver types without conflicts.

Methods for any receiver

A method may be declared for an arbitrary receiver by using an unknown symbol, typically T, as the receiver types. Such a method is applicable to any type.

Overloading by receiver type

Specific receiver types do not conflict:

Partial specialization for generic receivers

A method declared for a generic receiver may have specialized implementations for predefined types or patterns. Consider an iterator over a tuple of slices, where each slice encodes a value of type T:
For TupleIterator<int32> or TupleIterator<Point>, next() decodes the next slice and returns a value of type T. However, additional requirements are:
  • TupleIterator<slice> should return data[i] without calling fromSlice();
  • TupleIterator<Cell<T>> should unpack a cell and return T, not Cell<T>.
Tolk allows overloading methods for more specific receiver types:
Another example declares an extension method for map<K, V>, with specialized implementations for specific receiver patterns:
  • when V = K,
  • and when V is another map, the method behavior differs.

Auto-inline small functions

The compiler automatically inlines small functions in-place when possible. For example:
It is reduced to the following assembler code:

Attributes

A function or method may be preceded by one or several attributes:
The following attributes are allowed:
  • @inline forces a function to be inlined in-place. Typically unnecessary, as the compiler performs inlining automatically.
  • @inline_ref enables a special form of inlining where the function body is embedded as a child cell reference in the resulting bytecode.
  • @noinline turns off inlining for a function, for example, for a slow path.
  • @method_id(<number>) is a low-level annotation to manually override the TVM method_id.
  • @pure indicates that a function does not modify global state, including TVM state. If its result is unused, the call may be removed and typically used in assembler functions.
  • @deprecated marks a function as deprecated; it exists only for backward compatibility.
  • @custom(<anything>), @custom.xxx, or @custom.xxx(<anything>) adds a custom annotation with an arbitrary name that is not analyzed by the compiler.
  • @test(<anything>), @test.xxx, and @test.xxx(<anything>) is identical to @custom.
Both @custom and @test attributes are intended for external tools, such as analyzers and code generators.

Assembler functions

Tolk supports declaring low-level assembler functions with embedded Fift code:

Anonymous functions (lambdas)

Tolk supports first-class functions: they can be passed as callbacks. Both named functions and function expressions may be referenced:
Lambdas can capture outer variables by value, becoming closures.