Basic control-flow/declarations
This RFC lays down the fundamental control-flow and declaration structures for the language. It describes both the the syntax and intended semantics in a semi-formal way. The goal is to have even just some bare-bones elements that we can agree on and build the other RFCs and proposals on.
Function definition
This RFC introduces free-functions (functions that are not tied to types), member functions are not part of it.
Syntax
There will be two syntax variants, the "block syntax":
func function_name(arg1: Type1, arg2: Type2): ReturnType {
// Function body
}
And the "inline-expression" syntax (roughly equivalent to arrow-bodied methods in C#):
func function_name(arg1: Type1, arg2: Type2): ReturnType = expression;
The : ReturnType
part is optional for both syntaxes.
The parameter list can optionally contain a variadic argument list as the last parameter, which must have the ...parameterName: CollectionType
declaration syntax.
func function_name(arg1: Type1, arg2: Type2, ...args: Array<Type3>)
If a collection is passed as a variadic argument, the collection won't be passed as individual elements - like in C# -, but as a single element instead. If the given collection should be passed as the individual elements it contains, the spread operator must be used.
func foo(...args: Array<object>) {}
func main(){
val arr = arrayOf(1, 2, 3);
foo(arr); // The entire array is passed as a single element
foo(...arr); // The elements of the array are passed individually
}
Semantics
Omitting the return type means that the return type is unit
(equivalent to void
in C#).
func foo(): int32 {
return 0; // OK
}
func foo() {
return 0; // ERROR: integer returned, declared return type was unit
}
func foo(): int32 = 0; // OK
func foo() = 0; // ERROR: integer returned, declared return type was unit
Defining a local function inside another function is allowed.
func main(): int32 {
func first(a: int32, b: int32): int32 = a;
return first(1, 2); // OK
}
Variadic arguments can be used as regular collections inside the function - as their type specified. While currently only variadic argument lists with type Array<T>
are supported, in the future this can be extended to more collections and even spans. Calling a function that contains a variadic argument list allows the user to append an arbitrary number of arguments that has the same type as the element type of the variadic argument collection.
func foo(arg1: string, ...args: Array<int32>)
{
var result = 0;
var i = 0;
while(i < args.Length){
result += args[i];
i += 1;
}
return result;
}
func main(){
foo("Hi", 5); // OK
foo("Hello", 5, 10, 15, 25); // OK
foo("Hello"); // OK
foo(5, 10, 15, 25); // ERROR: all regular arguments still must be provided
}
Return
Return is an expression that evaluates to the unreachable
type.
Syntax
Return has two syntax variants, with an optional expression to return:
return // unit return value
return expr // some evaluated expression return value
Semantics
Return terminates the currently executed function and returns the value specified (or unit, if none was specified). If a return expression is omitted from a block-syntax function, it is assumed to return unit
at the very end.
Usage in expressions
The rationale behind return
being an expression with the unreachable
type is to be able to terminate computations early, while not tripping up type checking:
// If return was a statement, there would be an error here, saying that the 'else' branch has type 'unit', but the other branch has type 'int32'
var x = if (y % 2 == 0) y / 2 else return;
Goto
Goto is an expression that evaluates to the unreachable
type. Labels are statements (or rather, declarations).
Syntax
The syntax for a labels is:
label_name:
The syntax of a goto expression is:
goto label_name
Semantics
Executing a goto
means jumping the execution to the specified label. The jump can not jump into or out of the containing function (not even into contained or container functions).
Usage in expressions
The rationale behind it being an expression is similar to the return expression:
retry:
n = n / 2;
x += 1;
var mustBeZero = if (n == 0) x else goto retry;
Code blocks
Code-blocks are expressions that are able to evaluate to some value.
Syntax
{
statement1
statement2
// ...
expr
}
The expression at the end is optional.
Semantics
Code-blocks introduce a new lexical scope. Is there is an expression at the end of the code block, the block evaluates to that expression. If there is no expression at the end (it ends with a statement or the block is empty), it's implicitly assumed to evaluate to unit. The evaluation of block is done by executing all statements in it in sequence, finally evaluating the expression at the end as the result.
If-else
If-else is an expression, evaluating to some value.
Syntax
if (cond-expr) then-expr else els-expr
if (cond-expr) then-expr
The else branch is optional. Note, that since blocks are expressions, something like
if (cond-expr) { ... } else { ... }
is also valid, it requires no further specification.
Semantics
If the condition (cond-expr
) evaluates to true, then then-expr
is evaluated, else els-expr
is evaluated. If the else branch is omitted, it is assumed to evaluate to unit. The two branches must evaluate to compatible types (see the type-inference proposal for what this will mean exactly).
While loop
The while-loop is an expression, always evaluating to unit.
Syntax
while (cond-expr) body-expr
Note, that since blocks are expressions, something like
while (cond-expr) { ... }
is also valid, it requires no further specification.
Semantics
While the condition (cond-expr
) evaluates to true, body-expr
is evaluated. A while-loop always evaluates to unit, the body is simply "executed", not taken into account as the result.
The loop defines specialized labels, break
and continue
. If we desugar the loop into ifs and gotos, it would be equivalent to the following:
continue:
if (not cond-expr) goto break;
body-expr
break:
For nested loops, the innermost label simply shadows the outer ones. This means, that our break
and continue
statements are just goto
s with automatically generated labels and written as:
while (...) {
if (...) goto break;
if (...) goto continue;
}
Variables
Variables can be both function-local and global and they are statements (or rather, declarations).
Syntax
They come in two flavors, with the keyword var
and the keyword val
:
var var_name: VarType = VarValue;
val var_name: VarType = VarValue;
Both the type specification (: VarType
) and the value initializer (= VarValue
) are optional elements.
Semantics
var
introduces a mutable, and val
introduces an immutable variable. Local variables are order-dependent (meaning they can't be referenced before use), and global variables are order-independent. By #13, arbitrary variable shadowing is allowed, meaning that function-local variables can overwrite each other. Example:
var x = 1;
val x = x.ToString(); // From now on, this x is available and is immutable
{
var x = 1; // Inside here, x refers to the integer
}
// Here again, the immutable string is accessible
The syntax gives 4 combinations:
- Type specified, value specified: The specified values type has to be assignable to the specified type
- Only type specified: Variable has the exact type specified
- Only value specified: The type is inferred from the specified value and usage
- Nothing specified: The type will be inferred from use
On inference, see the issue.