Statements and expressions ========================== Nim uses the common statement/expression paradigm: Statements do not produce a value in contrast to expressions. However, some expressions are statements. Statements are separated into `simple statements`:idx: and `complex statements`:idx:. Simple statements are statements that cannot contain other statements like assignments, calls or the ``return`` statement; complex statements can contain other statements. To avoid the `dangling else problem`:idx:, complex statements always have to be indented. The details can be found in the grammar. Statement list expression ------------------------- Statements can also occur in an expression context that looks like ``(stmt1; stmt2; ...; ex)``. This is called an statement list expression or ``(;)``. The type of ``(stmt1; stmt2; ...; ex)`` is the type of ``ex``. All the other statements must be of type ``void``. (One can use ``discard`` to produce a ``void`` type.) ``(;)`` does not introduce a new scope. Discard statement ----------------- Example: .. code-block:: nim proc p(x, y: int): int = result = x + y discard p(3, 4) # discard the return value of `p` The ``discard`` statement evaluates its expression for side-effects and throws the expression's resulting value away. Ignoring the return value of a procedure without using a discard statement is a static error. The return value can be ignored implicitly if the called proc/iterator has been declared with the `discardable`:idx: pragma: .. code-block:: nim proc p(x, y: int): int {.discardable.} = result = x + y p(3, 4) # now valid An empty ``discard`` statement is often used as a null statement: .. code-block:: nim proc classify(s: string) = case s[0] of SymChars, '_': echo "an identifier" of '0'..'9': echo "a number" else: discard Void context ------------ In a list of statements every expression except the last one needs to have the type ``void``. In addition to this rule an assignment to the builtin ``result`` symbol also triggers a mandatory ``void`` context for the subsequent expressions: .. code-block:: nim proc invalid*(): string = result = "foo" "invalid" # Error: value of type 'string' has to be discarded .. code-block:: nim proc valid*(): string = let x = 317 "valid" Var statement ------------- Var statements declare new local and global variables and initialize them. A comma separated list of variables can be used to specify variables of the same type: .. code-block:: nim var a: int = 0 x, y, z: int If an initializer is given the type can be omitted: the variable is then of the same type as the initializing expression. Variables are always initialized with a default value if there is no initializing expression. The default value depends on the type and is always a zero in binary. ============================ ============================================== Type default value ============================ ============================================== any integer type 0 any float 0.0 char '\\0' bool false ref or pointer type nil procedural type nil sequence nil (*not* ``@[]``) string nil (*not* "") tuple[x: A, y: B, ...] (default(A), default(B), ...) (analogous for objects) array[0..., T] [default(T), ...] range[T] default(T); this may be out of the valid range T = enum cast[T](0); this may be an invalid value ============================ ============================================== The implicit initialization can be avoided for optimization reasons with the `noinit`:idx: pragma: .. code-block:: nim var a {.noInit.}: array [0..1023, char] If a proc is annotated with the ``noinit`` pragma this refers to its implicit ``result`` variable: .. code-block:: nim proc returnUndefinedValue: int {.noinit.} = discard The implicit initialization can be also prevented by the `requiresInit`:idx: type pragma. The compiler requires an explicit initialization then. However it does a `control flow analysis`:idx: to prove the variable has been initialized and does not rely on syntactic properties: .. code-block:: nim type MyObject = object {.requiresInit.} proc p() = # the following is valid: var x: MyObject if someCondition(): x = a() else: x = a() use x let statement ------------- A ``let`` statement declares new local and global `single assignment`:idx: variables and binds a value to them. The syntax is the same as that of the ``var`` statement, except that the keyword ``var`` is replaced by the keyword ``let``. Let variables are not l-values and can thus not be passed to ``var`` parameters nor can their address be taken. They cannot be assigned new values. For let variables the same pragmas are available as for ordinary variables. Tuple unpacking --------------- In a ``var`` or ``let`` statement tuple unpacking can be performed. The special identifier ``_`` can be used to ignore some parts of the tuple: .. code-block:: nim proc returnsTuple(): (int, int, int) = (4, 2, 3) let (x, _, z) = returnsTuple() Const section ------------- `Constants`:idx: are symbols which are bound to a value. The constant's value cannot change. The compiler must be able to evaluate the expression in a constant declaration at compile time. Nim contains a sophisticated compile-time evaluator, so procedures which have no side-effect can be used in constant expressions too: .. code-block:: nim import strutils const constEval = contains("abc", 'b') # computed at compile time! The rules for compile-time computability are: 1. Literals are compile-time computable. 2. Type conversions are compile-time computable. 3. Procedure calls of the form ``p(X)`` are compile-time computable if ``p`` is a proc without side-effects (see the `noSideEffect pragma <#pragmas-nosideeffect-pragma>`_ for details) and if ``X`` is a (possibly empty) list of compile-time computable arguments. Constants cannot be of type ``ptr``, ``ref``, ``var`` or ``object``, nor can they contain such a type. Static statement/expression --------------------------- A static statement/expression can be used to enforce compile time evaluation explicitly. Enforced compile time evaluation can even evaluate code that has side effects: .. code-block:: static: echo "echo at compile time" It's a static error if the compiler cannot perform the evaluation at compile time. The current implementation poses some restrictions for compile time evaluation: Code which contains ``cast`` or makes use of the foreign function interface cannot be evaluated at compile time. Later versions of Nim will support the FFI at compile time. If statement ------------ Example: .. code-block:: nim var name = readLine(stdin) if name == "Andreas": echo "What a nice name!" elif name == "": echo "Don't you have a name?" else: echo "Boring name..." The ``if`` statement is a simple way to make a branch in the control flow: The expression after the keyword ``if`` is evaluated, if it is true the corresponding statements after the ``:`` are executed. Otherwise the expression after the ``elif`` is evaluated (if there is an ``elif`` branch), if it is true the corresponding statements after the ``:`` are executed. This goes on until the last ``elif``. If all conditions fail, the ``else`` part is executed. If there is no ``else`` part, execution continues with the next statement. In ``if`` statements new scopes begin immediately after the ``if``/``elif``/``else`` keywords and ends after the corresponding *then* block. For visualization purposes the scopes have been enclosed in ``{| |}`` in the following example: .. code-block:: nim if {| (let m = input =~ re"(\w+)=\w+"; m.isMatch): echo "key ", m[0], " value ", m[1] |} elif {| (let m = input =~ re""; m.isMatch): echo "new m in this scope" |} else: {| echo "m not declared here" |} Case statement -------------- Example: .. code-block:: nim case readline(stdin) of "delete-everything", "restart-computer": echo "permission denied" of "go-for-a-walk": echo "please yourself" else: echo "unknown command" # indentation of the branches is also allowed; and so is an optional colon # after the selecting expression: case readline(stdin): of "delete-everything", "restart-computer": echo "permission denied" of "go-for-a-walk": echo "please yourself" else: echo "unknown command" The ``case`` statement is similar to the if statement, but it represents a multi-branch selection. The expression after the keyword ``case`` is evaluated and if its value is in a *slicelist* the corresponding statements (after the ``of`` keyword) are executed. If the value is not in any given *slicelist* the ``else`` part is executed. If there is no ``else`` part and not all possible values that ``expr`` can hold occur in a ``slicelist``, a static error occurs. This holds only for expressions of ordinal types. "All possible values" of ``expr`` are determined by ``expr``'s type. To suppress the static error an ``else`` part with an empty ``discard`` statement should be used. For non ordinal types it is not possible to list every possible value and so these always require an ``else`` part. As a special semantic extension, an expression in an ``of`` branch of a case statement may evaluate to a set or array constructor; the set or array is then expanded into a list of its elements: .. code-block:: nim const SymChars: set[char] = {'a'..'z', 'A'..'Z', '\x80'..'\xFF'} proc classify(s: string) = case s[0] of SymChars, '_': echo "an identifier" of '0'..'9': echo "a number" else: echo "other" # is equivalent to: proc classify(s: string) = case s[0] of 'a'..'z', 'A'..'Z', '\x80'..'\xFF', '_': echo "an identifier" of '0'..'9': echo "a number" else: echo "other" When statement -------------- Example: .. code-block:: nim when sizeof(int) == 2: echo "running on a 16 bit system!" elif sizeof(int) == 4: echo "running on a 32 bit system!" elif sizeof(int) == 8: echo "running on a 64 bit system!" else: echo "cannot happen!" The ``when`` statement is almost identical to the ``if`` statement with some exceptions: * Each condition (``expr``) has to be a constant expression (of type ``bool``). * The statements do not open a new scope. * The statements that belong to the expression that evaluated to true are translated by the compiler, the other statements are not checked for semantics! However, each condition is checked for semantics. The ``when`` statement enables conditional compilation techniques. As a special syntactic extension, the ``when`` construct is also available within ``object`` definitions. When nimvm statement -------------------- ``nimvm`` is a special symbol, that may be used as expression of ``when nimvm`` statement to differentiate execution path between runtime and compile time. Example: .. code-block:: nim proc someProcThatMayRunInCompileTime(): bool = when nimvm: # This code runs in compile time result = true else: # This code runs in runtime result = false const ctValue = someProcThatMayRunInCompileTime() let rtValue = someProcThatMayRunInCompileTime() assert(ctValue == true) assert(rtValue == false) ``when nimvm`` statement must meet the following requirements: * Its expression must always be ``nimvm``. More complex expressions are not allowed. * It must not contain ``elif`` branches. * It must contain ``else`` branch. * Code in branches must not affect semantics of the code that follows the ``when nimvm`` statement. E.g. it must not define symbols that are used in the following code. Return statement ---------------- Example: .. code-block:: nim return 40+2 The ``return`` statement ends the execution of the current procedure. It is only allowed in procedures. If there is an ``expr``, this is syntactic sugar for: .. code-block:: nim result = expr return result ``return`` without an expression is a short notation for ``return result`` if the proc has a return type. The `result`:idx: variable is always the return value of the procedure. It is automatically declared by the compiler. As all variables, ``result`` is initialized to (binary) zero: .. code-block:: nim proc returnZero(): int = # implicitly returns 0 Yield statement --------------- Example: .. code-block:: nim yield (1, 2, 3) The ``yield`` statement is used instead of the ``return`` statement in iterators. It is only valid in iterators. Execution is returned to the body of the for loop that called the iterator. Yield does not end the iteration process, but execution is passed back to the iterator if the next iteration starts. See the section about iterators (`Iterators and the for statement`_) for further information. Block statement --------------- Example: .. code-block:: nim var found = false block myblock: for i in 0..3: for j in 0..3: if a[j][i] == 7: found = true break myblock # leave the block, in this case both for-loops echo found The block statement is a means to group statements to a (named) ``block``. Inside the block, the ``break`` statement is allowed to leave the block immediately. A ``break`` statement can contain a name of a surrounding block to specify which block is to leave. Break statement --------------- Example: .. code-block:: nim break The ``break`` statement is used to leave a block immediately. If ``symbol`` is given, it is the name of the enclosing block that is to leave. If it is absent, the innermost block is left. While statement --------------- Example: .. code-block:: nim echo "Please tell me your password:" var pw = readLine(stdin) while pw != "12345": echo "Wrong password! Next try:" pw = readLine(stdin) The ``while`` statement is executed until the ``expr`` evaluates to false. Endless loops are no error. ``while`` statements open an `implicit block`, so that they can be left with a ``break`` statement. Continue statement ------------------ A ``continue`` statement leads to the immediate next iteration of the surrounding loop construct. It is only allowed within a loop. A continue statement is syntactic sugar for a nested block: .. code-block:: nim while expr1: stmt1 continue stmt2 Is equivalent to: .. code-block:: nim while expr1: block myBlockName: stmt1 break myBlockName stmt2 Assembler statement ------------------- The direct embedding of assembler code into Nim code is supported by the unsafe ``asm`` statement. Identifiers in the assembler code that refer to Nim identifiers shall be enclosed in a special character which can be specified in the statement's pragmas. The default special character is ``'`'``: .. code-block:: nim {.push stackTrace:off.} proc addInt(a, b: int): int = # a in eax, and b in edx asm """ mov eax, `a` add eax, `b` jno theEnd call `raiseOverflow` theEnd: """ {.pop.} If the GNU assembler is used, quotes and newlines are inserted automatically: .. code-block:: nim proc addInt(a, b: int): int = asm """ addl %%ecx, %%eax jno 1 call `raiseOverflow` 1: :"=a"(`result`) :"a"(`a`), "c"(`b`) """ Instead of: .. code-block:: nim proc addInt(a, b: int): int = asm """ "addl %%ecx, %%eax\n" "jno 1\n" "call `raiseOverflow`\n" "1: \n" :"=a"(`result`) :"a"(`a`), "c"(`b`) """ Using statement --------------- **Warning**: The ``using`` statement is experimental and has to be explicitly enabled with the `experimental`:idx: pragma or command line option! The using statement provides syntactic convenience in modules where the same parameter names and types are used over and over. Instead of: .. code-block:: nim proc foo(c: Context; n: Node) = ... proc bar(c: Context; n: Node, counter: int) = ... proc baz(c: Context; n: Node) = ... One can tell the compiler about the convention that a parameter of name ``c`` should default to type ``Context``, ``n`` should default to ``Node`` etc.: .. code-block:: nim {.experimental.} using c: Context n: Node counter: int proc foo(c, n) = ... proc bar(c, n, counter) = ... proc baz(c, n) = ... The ``using`` section uses the same indentation based grouping syntax as a ``var`` or ``let`` section. Note that ``using`` is not applied for ``template`` since untyped template parameters default to the type ``system.untyped``. If expression ------------- An `if expression` is almost like an if statement, but it is an expression. Example: .. code-block:: nim var y = if x > 8: 9 else: 10 An if expression always results in a value, so the ``else`` part is required. ``Elif`` parts are also allowed. When expression --------------- Just like an `if expression`, but corresponding to the when statement. Case expression --------------- The `case expression` is again very similar to the case statement: .. code-block:: nim var favoriteFood = case animal of "dog": "bones" of "cat": "mice" elif animal.endsWith"whale": "plankton" else: echo "I'm not sure what to serve, but everybody loves ice cream" "ice cream" As seen in the above example, the case expression can also introduce side effects. When multiple statements are given for a branch, Nim will use the last expression as the result value, much like in an `expr` template. Table constructor ----------------- A table constructor is syntactic sugar for an array constructor: .. code-block:: nim {"key1": "value1", "key2", "key3": "value2"} # is the same as: [("key1", "value1"), ("key2", "value2"), ("key3", "value2")] The empty table can be written ``{:}`` (in contrast to the empty set which is ``{}``) which is thus another way to write as the empty array constructor ``[]``. This slightly unusual way of supporting tables has lots of advantages: * The order of the (key,value)-pairs is preserved, thus it is easy to support ordered dicts with for example ``{key: val}.newOrderedTable``. * A table literal can be put into a ``const`` section and the compiler can easily put it into the executable's data section just like it can for arrays and the generated data section requires a minimal amount of memory. * Every table implementation is treated equal syntactically. * Apart from the minimal syntactic sugar the language core does not need to know about tables. Type conversions ---------------- Syntactically a `type conversion` is like a procedure call, but a type name replaces the procedure name. A type conversion is always safe in the sense that a failure to convert a type to another results in an exception (if it cannot be determined statically). Ordinary procs are often preferred over type conversions in Nim: For instance, ``$`` is the ``toString`` operator by convention and ``toFloat`` and ``toInt`` can be used to convert from floating point to integer or vice versa. Type casts ---------- Example: .. code-block:: nim cast[int](x) Type casts are a crude mechanism to interpret the bit pattern of an expression as if it would be of another type. Type casts are only needed for low-level programming and are inherently unsafe. The addr operator ----------------- The ``addr`` operator returns the address of an l-value. If the type of the location is ``T``, the `addr` operator result is of the type ``ptr T``. An address is always an untraced reference. Taking the address of an object that resides on the stack is **unsafe**, as the pointer may live longer than the object on the stack and can thus reference a non-existing object. One can get the address of variables, but one can't use it on variables declared through ``let`` statements: .. code-block:: nim let t1 = "Hello" var t2 = t1 t3 : pointer = addr(t2) echo repr(addr(t2)) # --> ref 0x7fff6b71b670 --> 0x10bb81050"Hello" echo cast[ptr string](t3)[] # --> Hello # The following line doesn't compile: echo repr(addr(t1)) # Error: expression has no address