Tolk vs FunC: in detail
A huge list is below. Will anyone have enough patience to read it up to the end?..
Here: Tolk vs FunC: in short
✅ Traditional comments :)
FunC | Tolk |
---|---|
;; comment | // comment |
{- multiline comment -} | /* multiline comment */ |
✅ 2+2
is 4, not an identifier. Identifiers can only be alpha-numeric
In FunC, almost any character can be a part of the identifier.
For example, 2+2
(without a space) is an identifier.
You can even declare a variable with such a name.
In Tolk, spaces are not mandatory. 2+2
is 4, as expected. 3+~x
is 3 + (~ x)
, and so on.
FunC | Tolk |
---|---|
return 2+2; ;; undefined function `2+2` | return 2+2; // 4 |
More precisely, an identifier can start from [a-zA-Z$_]
and be continued with [a-zA-Z0-9$_]
. Note that ?
, :
, and others are not valid symbols, and found?
and op::increase
are invalid identifiers.
Note, that cell
, slice
, etc. are valid identifiers: var cell = ...
or even var cell: cell = ...
is okay. (like in TypeScript, number
is a valid identifier)
You can use backticks to surround an identifier, and then it can contain any symbols (similar to Kotlin and some other languages). This potential usage is to allow keywords to be used as identifiers in case of code generation by a scheme, for example.
FunC | Tolk |
---|---|
const op::increase = 0x1234; | const OP_INCREASE = 0x1234; |
;; even 2%&!2 is valid | // don't do like this :) |
✅ Impure by default, compiler won't drop user function calls
FunC has an impure
function specifier. When absent, a function is treated as pure. If its result is unused, its call is deleted by the compiler.
Though this behavior is documented, it is very unexpected to newcomers. For instance, various functions that don't return anything (throw an exception on mismatch, for example) are silently deleted. This situation is spoilt by FunC not checking and validating the function body, allowing impure operations inside pure functions.
In Tolk, all functions are impure by default. You can mark a function pure with annotation, and then impure operations are forbidden in its body (exceptions, globals modification, calling non-pure functions, etc.).
✅ New functions syntax: fun
keyword, @
attributes, types on the right (like in TypeScript, Kotlin, Python, etc.)
FunC | Tolk |
---|---|
cell parse_data(slice cs) { } | fun parse_data(cs: slice): cell { } |
(cell, int) load_storage() { } | fun load_storage(): (cell, int) { } |
() main() { ... } | fun main() { ... } |
Types of variables — also to the right:
FunC | Tolk |
---|---|
slice cs = ...; | var cs: slice = ...; |
(cell c, int n) = parse_data(cs); | var (c: cell, n: int) = parse_data(cs); |
global int stake_at; | global stake_at: int; |
Modifiers inline
and others — with annotations:
FunC | Tolk |
---|---|
| @inline |
| @inline_ref |
global int stake_at; | global stake_at: int; |
forall
— this way:
FunC | Tolk |
---|---|
forall X -> tuple cons(X head, tuple tail) | fun cons<X>(head: X, tail: tuple): tuple |
asm
implementation — like in FunC, but being properly aligned, it looks nicer:
@pure
fun third<X>(t: tuple): X
asm "THIRD";
@pure
fun iDictDeleteGet(dict: cell, keyLen: int, index: int): (cell, slice, int)
asm(index dict keyLen) "DICTIDELGET NULLSWAPIFNOT";
@pure
fun mulDivFloor(x: int, y: int, z: int): int
builtin;
There is also a @deprecated
attribute, not affecting compilation, but for a human and IDE.
✅ get
instead of method_id
In FunC, method_id
(without arguments) declared a get method. In Tolk, you use a straightforward syntax:
FunC | Tolk |
---|---|
int seqno() method_id { ... } | get seqno(): int { ... } |
Both get methodName()
and get fun methodName()
are acceptable.
For method_id(xxx)
(uncommon in practice, but valid), there is an attribute:
FunC | Tolk |
---|---|
| @method_id(1666) |
✅ It's essential to declare types of parameters (though optional for locals)
// not allowed
fun do_smth(c, n)
// types are mandatory
fun do_smth(c: cell, n: int)
If parameter types are mandatory, the return type is not (it's often obvious or verbose). If omitted, it's auto-inferred:
fun x() { ... } // auto infer from return statements
For local variables, types are also optional:
var i = 10; // ok, int
var b = beginCell(); // ok, builder
var (i, b) = (10, beginCell()); // ok, two variables, int and builder
// types can be specified manually, of course:
var b: builder = beginCell();
var (i: int, b: builder) = (10, beginCell());
✅ Variables are not allowed to be redeclared in the same scope
var a = 10;
...
var a = 20; // error, correct is just `a = 20`
if (1) {
var a = 30; // it's okay, it's another scope
}
As a consequence, partial reassignment is not allowed:
var a = 10;
...
var (a, b) = (20, 30); // error, releclaration of a
Note, that it's not a problem for loadUint()
and other methods. In FunC, they returned a modified object, so a pattern var (cs, int value) = cs.load_int(32)
was quite common. In Tolk, such methods mutate an object: var value = cs.loadInt(32)
, so redeclaration is unlikely to be needed.
fun send(msg: cell) {
var msg = ...; // error, redeclaration of msg
// solution 1: intruduce a new variable
var msgWrapped = ...;
// solution 2: use `redef`, though not recommended
var msg redef = ...;
✅ String postfixes removed, compile-time functions introduced
Tolk removes the old FunC-style string postfixes ("..."c
, etc.) in favor of a more transparent and more flexible approach.
FunC | Tolk |
---|---|
"..."c | stringCrc32("...") |
— | stringCrc16("...") |
"..."H | stringSha256("...") |
"..."h | stringSha256_32("...") |
"..."a | stringAddressToSlice("...") |
"..."s | stringHexToSlice("...") |
"..."u | stringToBase256("...") |
These functions:
- compile-time only
- for constant strings only
- can be used in constant initialization
// type will be `slice`
const BASIC_ADDR = stringAddressToSlice("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF");
// return type will be `int`
fun minihashDemo() {
return stringSha256_32("transfer(slice, int)");
}
The naming highlights that these functions have arrived from string postfixes and operate on string values. Remember that at runtime, there are no strings, only slices.
✅ Trailing comma support
Tolk now supports trailing commas in the following contexts:
- tensors
- tuples
- function calls
- function parameters
var items = (
totalSupply,
verifiedCode,
validatorsList,
);
Note that (5)
is not a tensor. It's just the integer 5
in parentheses.
With a trailing comma (5,)
it's still (5)
.
✅ Optional semicolon for the last statement in a block
In Tolk, you can omit the semicolon after the final statement in a block. While semicolons are still required between statements, the trailing semicolon on the last statement is now optional.
fun f(...) {
doSomething();
return result // <-- valid without semicolon
}
// or
if (smth) {
return 1
} else {
return 2
}
✅ Function ton("...")
for human-readable amounts of Toncoins
FunC | Tolk |
---|---|
int cost = 50000000; | val cost = ton("0.05"); |
const ONE_TON = 1000000000; | const ONE_TON = ton("1"); |
The function ton()
only accepts constant values (e.g., ton(some_var)
is invalid).
Its type is coins
(not int
!), although it's still a regular int
from the TVM point of view.
Arithmetic over coins
degrade to int
(for example, cost << 1
is valid, cost + ton("0.02")
also).
✅ Changes in the type system
FunC's type system is based on Hindley-Milner. This is a common approach for functional languages, where types are inferred from usage through unification.
In Tolk v0.7, the type system is rewritten from scratch. In order to add booleans, fixed-width integers, nullability, structures, and generics, we must have a static type system (like TypeScript or Rust). Because Hindley-Milner will clash with structure methods, struggle with proper generics, and become entirely impractical for union types (despite claims that it was "designed for union types").
We have the following types:
int
,bool
,cell
,slice
,builder
, untypedtuple
- typed tuple
[T1, T2, ...]
- tensor
(T1, T2, ...)
- callables
(TArgs) -> TResult
- nullable types
T?
, compile-time null safety - union types
T1 | T2 | ...
, handled with pattern matching coins
and functionton("0.05")
int32
,uint64
, and other fixed-width integers (just int at TVM) detailsbytesN
andbitsN
, similar tointN
(backed by slices at TVM)void
(more canonical to be namedunit
, butvoid
is more reliable)self
, to make chainable methods, described below; actually it's not a type, it can only occur instead of return type of a functionnever
(an always-throwing function returnsnever
, for example; an "impossible type" is alsonever
)
The type system obeys the following rules:
- variable types can be specified manually or are inferred from declarations, and never change after being declared
- function parameters must be strictly typed
- function return types, if unspecified, inferred from return statements similar to TypeScript; in case of recursion (direct or indirect), the return type must be explicitly declared somewhere
- generic functions are supported