Functions
Functions are sequences of statements that perform a specific task. Functions have parameters (inputs) and an optional return value (output). Functions are typed: the function type consists of the parameter types and the return type.
Functions are values, i.e., they can be assigned to constants and variables, and can be passed as arguments to other functions. This behavior is often called "first-class functions".
Function Declarations
Functions can be declared by using the fun
keyword, followed by the name of the declaration,
the parameters, the optional return type,
and the code that should be executed when the function is called.
The parameters need to be enclosed in parentheses.
The return type, if any, is separated from the parameters by a colon (:
).
The function code needs to be enclosed in opening and closing braces.
Each parameter must have a name, which is the name that the argument value will be available as within the function.
An additional argument label can be provided to require function calls to use the label to provide an argument value for the parameter.
Argument labels make code more explicit and readable. For example, they avoid confusion about the order of arguments when there are multiple arguments that have the same type.
Argument labels should be named so they make sense from the perspective of the function call.
Argument labels precede the parameter name.
The special argument label _
indicates
that a function call can omit the argument label.
If no argument label is declared in the function declaration,
the parameter name is the argument label of the function declaration,
and function calls must use the parameter name as the argument label.
Each parameter needs to have a type annotation, which follows the parameter name after a colon.
Function calls may provide arguments for parameters which are subtypes of the parameter types.
There is no support for optional parameters, i.e. default values for parameters, and variadic functions, i.e. functions that take an arbitrary amount of arguments.
It is possible to require argument labels for some parameters, and not require argument labels for other parameters.
The order of the arguments in a function call must match the order of the parameters in the function declaration.
Functions can be nested, i.e., the code of a function may declare further functions.
Functions do not support overloading.
Function Expressions
Functions can be also used as expressions. The syntax is the same as for function declarations, except that function expressions have no name, i.e., they are anonymous.
View Functions
Functions can be annotated as view
to indicate that they do not modify any external state or any account state.
A view
annotation can be added to the beginning of a function declaration or expression like so:
All functions that do not have a view
annotation are considered non-view,
and cannot be called inside of view
contexts,
like inside of a view
function or in a precondition or postcondition.
Function types can also have view
annotations,
to be placed after the opening parenthesis but before the parameter list.
So, for example, these are valid types:
Any function types without a view
annotation will be considered non-view.
Functions are covariant with respect to view
annotations,
so a view
function is a subtype of an non-view function with the same parameters and return types.
So, the following declarations would typecheck:
while these would not:
The operations that are not permitted in view
contexts are:
- Calling a non-view function (including any functions that modify account state or storage like
save
orload
) - Writing to or modifying any resources
- Writing to or modifying any references
- Indexed assignment or writes to any variables not statically knowable to have been defined in the current function's scope, or to any resources or references
So, for example, this code would be allowed:
while this would not:
A caveat to this is that in some cases a non-view
function that only performs a mutation that would be allowed in a view
context will be rejected as a limitation of the analysis.
In particular, users may encounter this with arrays or dictionaries, where a function like:
is acceptable, because a
is local to this function, while
will be rejected, because append
is not view
.
Function Calls
Functions can be called (invoked). Function calls need to provide exactly as many argument values as the function has parameters.
Function Types
Function types consist of the function's parameter types and the function's return type.
The parameter types need to be enclosed in parentheses,
followed by a colon (:
), and end with the return type.
The whole function type needs to be enclosed in parentheses.
If the function has no return type, it implicitly has the return type Void
.
Parentheses also control precedence.
For example, a function type fun(Int): fun(): Int
is the type
for a function which accepts one argument with type Int
,
and which returns another function,
that takes no arguments and returns an Int
.
The type [fun(Int): Int; 2]
specifies an array type of two functions,
which accept one integer and return one integer.
Argument labels are not part of the function type. This has the advantage that functions with different argument labels, potentially written by different authors are compatible as long as the parameter types and the return type match. It has the disadvantage that function calls to plain function values, cannot accept argument labels.
Closures
A function may refer to variables and constants of its outer scopes in which it is defined. It is called a closure, because it is closing over those variables and constants. A closure can read from the variables and constants and assign to the variables it refers to.
Argument Passing Behavior
When arguments are passed to a function, they are copied. Therefore, values that are passed into a function are unchanged in the caller's scope when the function returns. This behavior is known as call-by-value.
Parameters are constant, i.e., it is not allowed to assign to them.
Function Preconditions and Postconditions
Functions may have preconditions and may have postconditions. Preconditions and postconditions can be used to restrict the inputs (values for parameters) and output (return value) of a function.
Preconditions must be true right before the execution of the function.
Preconditions are part of the function and introduced by the pre
keyword,
followed by the condition block.
Postconditions must be true right after the execution of the function.
Postconditions are part of the function and introduced by the post
keyword,
followed by the condition block.
Postconditions may only occur after preconditions, if any.
A conditions block consists of one or more conditions. Conditions are expressions evaluating to a boolean.
Conditions may be written on separate lines, or multiple conditions can be written on the same line, separated by a semicolon. This syntax follows the syntax for statements.
Following each condition, an optional description can be provided after a colon. The condition description is used as an error message when the condition fails.
In postconditions, the special constant result
refers to the result of the function.
In postconditions, the special function before
can be used
to get the value of an expression just before the function is called.
Both preconditions and postconditions are considered view
contexts;
any operations that are not legal in functions with view
annotations are also not allowed in conditions.
In particular, this means that if you wish to call a function in a condition, that function must be view
.
Functions are Values
Functions are values ("first-class"), so they may be assigned to variables and fields or passed to functions as arguments.