Definitions
var
a = "a" # l-value
const
b = "a" # not l-value
let
c = "a" # not l-value
var
p: ptr string
echo(repr(a))
# --> 0x7fd3b9d09050"a"
p = addr(a)
echo(repr(p))
# --> ref 0x622958 --> 0x7fd3b9d09050"a"
#p = addr(b)
# static error: expression has no address
#p = addr(b)
# static error: expression has no address
Lexical Analysis
Encoding
All Nim source files are in the UTF-8 encoding (or its ASCII subset). Other encodings are not supported.
Indentation
Comments
var
x = 1 # comment piece
y = 1 # [comment start] comment piece
# comment piece
# comment piece [comment end]
## Documentation comment
## Documentation comment
Identifiers & Keywords
letter ::= 'A'..'Z' | 'a'..'z' | '\x80'..'\xff'
digit ::= '0'..'9'
IDENTIFIER ::= letter ( ['_'] (letter | digit) )*
Two immediate following underscores __
are not allowed
Identifier equality
a[0] == b[0] and a.replace("_", "").toLower == b.replace("_", "").toLower
String literals
Triple quoted string literals
When the opening """
is followed by a newline (there may be whitespace between the opening """
and the newline), the newline (and the preceding whitespace) is not included in the string.
assert """
"aaa"
bbb
ccc
\n\r\c\x00\t
""" == " \"aaa\"\nbbb\nccc\n\\n\\r\\c\\x00\\t\n"
Raw string literals
assert r"C:\texts\text.txt" == "C:\\texts\\text.txt"
assert r"a""b" == "a\"b"
Generalized raw string literals
identifier"string literal"
is a shortcut for
identifier(r"string literal")
identifier"""string literal"""
is a shortcut for
identifier("""string literal""")
Character literals
Character literals are enclosed in single quotes ''
. Newline (\n
) is not allowed as it may be wider than one character (often it is the pair CR/LF
for example).
A character is not an Unicode character but a single byte.Nim can thus support array[char, int]
or set[char]
efficiently as many algorithms rely on this feature.
Numerical constants
var
i = 3 # signed int
i8 = 3'i8 # signed int8
i16 = 3'i16 # signed int16
i32 = 3'i32 # signed int32
i64 = 3'i64 # signed int64
ui = 3'u # unsigned int
ui8 = 3'u8 # unsigned int8
ui16 = 3'u16 # unsigned int16
ui32 = 3'u32 # unsigned int32
ui64 = 3'u64 # unsigned int64
f = 0.0 # float
f32 = 0.0'f32 # float32
f64 = 0.0'f64 # float6
assert 0b11111111 == 255
assert 0o377 == 255
assert 0xff == 255
assert 0b0_10001110100_0000101001000111101011101111111011000101001101001001'f64 == 1.7282561e+35
assert 1_999_999 == 1999999
Size of int/uint is "size_t".
Operators
= + - * / < >
@ $ ~ & % |
! ? ^ . : \
and or not xor shl shr div mod in notin is isnot of
Other tokens
Types
- ordinal types
- integer (except for uint/uint64)
- bool
- char
- enum
- and subranges thereof
- float
- string
- structured types
- array
- seq
- set
- openArray
- varargs
- tuple
- object
- ref (ptr) type
- procedural type
- generic type
Ordinal types
- countable and ordered
- inc(), dec()
- ord(), low(), high(), pred(), succ()
var
i = 0
inc(i)
assert i == 1
dec(i)
assert i == 0
i = high(int)
assert i == high(i)
try:
inc(i)
except OverFlowError:
# Error: unhandled exception: over- or underflow [OverflowError]
echo("OverFlowError:", getCurrentExceptionMsg())
i = low(int)
assert i == low(i)
try:
dec(i)
except OverFlowError:
# Error: unhandled exception: over- or underflow [OverflowError]
echo("OverFlowError:", getCurrentExceptionMsg())
import unsigned
# some ops requires unsigned module
var
u:uint = 0
inc(u)
assert u == 1
dec(u)
assert u == 0
#u = low(uint)
#u = low(u)
# static error: invalid argument for `low`
#u = high(uint)
#u = high(u)
# static error: invalid argument for `high`
u = uint(high(int) * 2 + 1)
inc(u)
assert u == 0
dec(u)
assert u == uint(high(int) * 2 + 1)
var
c = 'm'
assert ord(c) == 109
assert chr(109) == c
assert pred('m') == 'l'
assert succ('m') == 'n'
inc(c)
assert c == 'n'
dec(c)
assert c == 'm'
c = low(c)
assert c == '\0'
try:
dec(c)
except OverFlowError:
# Error: unhandled exception: over- or underflow [OverflowError]
echo("OverFlowError:", getCurrentExceptionMsg())
c = high(c)
assert c == '\255'
try:
inc(c)
except OverFlowError:
# Error: unhandled exception: over- or underflow [OverflowError]
echo("OverFlowError: ", getCurrentExceptionMsg())
Pre-defined integer types
import unsigned
var
i: int
ui: uint
assert i == 0
assert ui == 0
Subrange type
A subrange type is a range of values from an ordinal type(the base type). To define a subrange type, one must specify it's limiting values: the lowest and highest value of the type.
A subrange type of a base ordinal type which can only hold the lowest value to highest value. Assignments from the base ordinal type to one of its subrange types (and vice versa) are allowed.
A subrange type has the same size as its base type (int in the example).
type
IntSubRange = range[-10 .. 10]
ChrSubRange = range['a' .. 'c']
Direction {.pure.} = enum
north,
south,
east,
west,
EnumSubRange = range[Direction.north .. Direction.east]
var
s_i: IntSubRange
s_c: ChrSubRange
s_e: EnumSubrange
i: int = s_i
assert s_i == 0
assert s_c == '\x0'
assert s_e == Direction.north
s_i = -10
s_c = 'b'
s_e = Direction.south
#s_i = -11
# static error: conversion from int literal(-11) to intSubRange is invalid
try:
dec(s_i)
except OverFlowError:
# Error: unhandled exception: over- or underflow [OverflowError]
echo("OverFlowError: ", getCurrentExceptionMsg())
Nim requires interval arithmetic for subrange types over a set of built-in operators that involve constants: x %% 3
is of type range[0..2]
. The following built-in operators for integers are affected by this rule: -
, +
, *
, min
, max
, succ
, pred
, mod
, div
, %%
, and
(bitwise and
).
Bitwise and
only produces a range
if one of its operands is a constant x
so that (x+1)
is a number of two. (Bitwise and
is then a %%
operation.)
case (x and 3) + 7
of 7: echo "A"
of 8: echo "B"
of 9: echo "C"
of 10: echo "D"
# note: no ``else`` required as (x and 3) + 7 has the type: range[7..10]
Pre-defined floating point types
Arithmetic performed on floating point types follows the IEEE standard. Integer types are not converted to floating point types automatically and vice versa.
The IEEE standard defines five types of floating-point exceptions:
- Invalid: operations with mathematically invalid operands, for example
0.0/0.0
,sqrt(-1.0)
, andlog(-37.8)
. - Division by zero: divisor is zero and dividend is a finite nonzero number, for example
1.0/0.0
. - Overflow: operation produces a result that exceeds the range of the exponent, for example
MAXDOUBLE+0.0000000000001e308
. - Underflow: operation produces a result that is too small to be represented as a normal number, for example,
MINDOUBLE * MINDOUBLE
. - Inexact: operation produces a result that cannot be represented with infinite precision, for example,
2.0 / 3.0
,log(1.1)
and0.1
in input.
The IEEE exceptions are either ignored at runtime or mapped to the Nim exceptions:
- FloatInvalidOpError
- FloatDivByZeroError
- FloatOverflowError
- FloatUnderflowError
- FloatInexactError
These exceptions inherit from the FloatingPointError
base class.
Nim provides the pragmas NaNChecks
and InfChecks
to control whether the IEEE exceptions are ignored or trap a Nim exception:
# https://akehrer.github.io/nim/2015/01/14/getting-started-with-nim-pt2.html
import math
const
Nan = 0.0/0.0 # floating point not a number (NaN)
proc cIsNaN(x: float): cint {.importc: "isnan", header: "<math.h>".}
## returns non-zero if x is not a number
proc cIsInf(x: float): cint {.importc: "isinf", header: "<math.h>".}
## returns non-zero if x is infinity
proc isNaN*(x: float): bool =
## converts the integer result from cIsNaN to a boolean
if cIsNaN(x) != 0.cint:
true
else:
false
proc isInf*(x: float): bool =
## converts the integer result from cIsInf to a boolean
if cIsInf(x) != 0.cint:
true
else:
false
var
a:float
b = 1.0
assert a == 0.0
echo(0.0 / 0.0) # --> nan
echo(1.0 / 0.0) # --> inf
echo(a / a) # --> -nan
echo(b / a) # --> inf
{.push nanChecks: on, infChecks: on.}
echo(0.0 / 0.0)
echo(1.0 / 0.0)
try:
echo a / a
except FloatInvalidOpError:
# Error: unhandled exception: FPU operation caused a NaN result [FloatInvalidOpError]
echo("FloatInvalidOpError: ", getCurrentExceptionMsg())
try:
echo b / a
except FloatOverflowError:
# Error: unhandled exception: FPU operation caused an overflow [FloatOverflowError]
echo("FloatOverflowError: ", getCurrentExceptionMsg())
{.pop.}
In the current implementation FloatDivByZeroError
and FloatInexactError
are never raised. FloatOverflowError
is raised instead of FloatDivByZeroError
. There is also a floatChecks
pragma that is a short-cut for the combination of NaNChecks
and InfChecks
pragmas. floatChecks
are turned off as default.
The only operations that are affected by the floatChecks
pragma are the +
, -
, *
, /
operators for floating point types.
An implementation should always use the maximum precision available to evaluate floating pointer values at compile time; this means expressions like 0.09'f32 + 0.01'f32 == 0.09'f64 + 0.01'f64
are true.
Boolean type
assert ord(false) == 0
assert ord(true) == 1
Character type
Nim can support array[char, int]
or set[char]
efficiently as many algorithms rely on this feature.
var
c: char
assert c == '\x0'
var
char_index_array: array[char, int]
assert char_index_array.len == 256
assert char_index_array['a'] == 0
char_index_array['v'] = 1
assert char_index_array['v'] == 1
var
char_set: set[char] = {'a', 'a', 'b'}
assert char_set == {'a', 'b'}
var
char_buffer: array[0 .. 255, char]
assert char_buffer.len == 256
assert char_buffer == ['\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0']
char_buffer[0] = 'a'
assert cstring(char_buffer).len == 1
echo cstring(char_buffer)
# --> a
Enumeration type
Enumeration types define a new type whose values consist of the ones specified. The values are ordered.
type
Direction = enum
north,
south,
east,
west,
assert north == Direction.north
assert north < Direction.south
assert ord(Direction.north) == 0
assert ord(north) == 0
assert ord(south) == 1
assert ord(east) == 2
assert ord(west) == 3
assert pred(east) == south
assert succ(east) == west
var
e: Direction
assert e == north
e = east
inc(e)
assert e == west
dec(e)
assert e == east
assert low(Direction) == north
assert low(e) == north
assert high(Direction) == west
assert high(e) == west
assert Direction(0) == north
assert Direction(1) == south
assert Direction(2) == east
assert Direction(3) == west
var
arr_e0: array[Direction, float]
arr_e1: array[north .. west, float]
assert arr_e0[east] == 0.0
assert arr_e1[east] == 0.0
arr_e0[east] = 1.1
type
ScopedEnum {.pure.} = enum
val_a,
val_b,
# static error: undeclared identifier: 'val_a'
#echo val_a
The fields of enum types can be assigned an explicit ordinal value. However, the ordinal values have to be in ascending order. A field whose ordinal value is not explicitly given is assigned the value of the previous field + 1.
An explicit ordered enum can have holes. However, it is then not an ordinal anymore, so it is not possible to use these enums as an index type for arrays. The procedures inc
, dec
, succ
and pred
are not available for them either.
type
Status = enum
ok = 200,
created,
accepted = "Accepted",
multiple_sources = 300,
moved_permanently = "Moved Permanently",
bad_request = 400,
internal_server_error = (500, "Internal Server Error")
assert ord(ok) == 200
assert ord(created) == 201
assert ord(accepted) == 202
# static error: enum 'Status' has holes
#var
# x: array[ok .. accepted, int]
# y: array[Status, int]
var
status = ok
inc(status)
assert status == created
inc(status)
assert status == accepted
inc(status)
assert status != multiple_sources
echo(status)
# --> 203 (invalid data!)
String type
Zero(Null)-terminated and have a length field. The length never counts the terminating zero. The assignment operator for strings always copies the string. The & operator concatenates strings.
Strings are compared by their lexicographical order. All comparison operators are available. Strings can be indexed like arrays (lower bound is 0).
var
s: string
assert s.isNil
s = "this" # assignment op does COPY the string
var
conc = s & " is" # concat
assert s[0] == 't'
# static error
#assert s[0] == "t"
s[0] = 'T'
# static error
#s[0] = "T"
assert s[0 .. 1] == "Th"
s.add(" ")
s.add("is a string")
assert s == "This is a string"
assert s[low(s) .. high(s)] == "This is a string"
try:
echo s[100]
except IndexError:
# Error: unhandled exception: index out of bounds [IndexError]
echo("IndexError: ", getCurrentExceptionMsg())
var
s = "0123456789"
assert s.len == 10
assert s[0] == '0'
assert s[^0] == '\x0'
assert s[^1] == '9'
assert s[1 .. 1] == "1"
assert s[1 .. 3] == "123"
assert s[^1 .. ^1] == "9"
assert s[^3 .. ^1] == "789"
assert s[^1 .. ^2] == ""
assert s[0 .. ^0] == "0123456789"
assert s[0 .. ^1] == "0123456789"
s[0] = 'a'
assert s == "a123456789"
s[0 .. 0] = "a"
assert s == "a123456789"
s[0 .. 2] = "abc"
assert s == "abc3456789"
s[^3 .. ^1] = "xyz"
assert s == "abc3456xyz"
s[0 .. 2] = "a"
assert s == "a3456xyz"
s[0 .. 2] = ""
assert s == "56xyz"
s[0 .. 0] = "012345"
assert s == "0123456xyz"
s[0 .. 2] = ""
assert s == "3456xyz"
s[^3 .. ^1] = ""
assert s == "3456"
s.setLen(1)
assert s.len == 1
assert s == "3"
Per convention, all strings are UTF-8 strings, but this is not enforced. For example, when reading strings from binary files, they are merely a sequence of bytes. The index operation s[i] means the i-th char of s, not the i-th unichar.
cstring type
A pointer to zero-terminated char array(char* in c). No boundChecks
. The index operation is unsafe.
A Nim string is implicitly convertible to cstring for convenience. If a Nim string is passed to a C-style variadic proc, it is implicitly converted to cstring too:
Even though the conversion is implicit, it is not safe: The garbage collector does not consider a cstring to be a root and may collect the underlying memory. However in practice this almost never happens as the GC considers stack roots conservatively. One can use the builtin procs GC_ref
and GC_unref
to keep the string data alive for the rare cases where it does not work.
A $
proc is defined for cstrings that returns a string. Thus to get a nim string from a cstring:
proc printf(formatstr: cstring) {.importc:
"printf", varargs,
header: "<stdio.h>".}
printf("This works as expected %s", "by printf in stdio")
var
str: string = "hello"
cstr: cstring
assert cstr.isNil
cstr = str
var
str_back: string = $cstr
var
# `system.cstringArray` is equal to `ptr array [0..ArrayDummySize, cstring]`
cstring_arr: cstringArray = allocCStringArray(["abc"])
cstring_arr[1] = "def"
cstring_arr[2] = "ghi"
assert cstringArrayToSeq(cstring_arr) == @["abc", "def", "ghi"]
dealloc(cstring_arr)
# if the members of a cstringArray are allocated (for example, by c), call `deallocCStringArray()`
#deallocCStringArray(cstring_arr)
Structured types
Arrays, sequences, tuples, objects and sets belong to the structured types.
Array and sequence types
array
- element: homogeneous
- length: fixed
- index type: ordinal type
- expression constructor: []
- boundsCheck: yes(compile-time)
import typetraits
var
arr_obj: array[0 .. 5, int]
arr_ref: ref array[0 .. 5, int]
assert arr_obj.type.name == "array[0..5, int]"
echo repr(arr_obj)
# --> [0, 0, 0, 0, 0, 0]
assert arr_ref.isNil
new(arr_ref)
assert (not arr_ref.isNil)
assert arr_ref.type.name == "ref array[0..5, int]"
echo repr(arr_ref)
# --> ref 0x7fda81f0b0d0 --> [0, 0, 0, 0, 0, 0]
var
ii_arr0: array[int, int]
# too big array
assert low(ii_arr0) == low(int)
assert high(ii_arr0) == high(int)
assert len(ii_arr0) == 0
assert ii_arr0[0] == 0
ii_arr0[0] = 0
assert ii_arr0.len == 0
var
i16i_arr: array[int16, int]
assert low(i16i_arr) == low(int16)
assert high(i16i_arr) == high(int16)
assert len(i16i_arr) == 65536
var
ii_arr1: array[0 .. 3, int]
assert low(ii_arr1) == 0
assert high(ii_arr1) == 3
assert len(ii_arr1) == 4
assert ii_arr1[0] == 0
ii_arr1 = [0, 1, 2, 3]
var
ci_arr: array[char, int]
assert low(ci_arr) == low(char)
assert high(ci_arr) == high(char)
assert len(ci_arr) == 256
assert ci_arr['a'] == 0
ci_arr['v'] = 1
type
Direction = enum
north,
south,
east,
west,
var
ei_arr0: array[Direction, int]
ei_arr1: array[north .. west, int]
assert low(ei_arr0) == north
assert high(ei_arr0) == west
assert len(ei_arr0) == 4
assert ei_arr0[north] == 0
ei_arr0[north] = 1
seq
- element: homogeneous
- length: dynamic
- index type: ordinal type. From
0
tolen(S) - 1
- boundsCheck: yes
- expression constructor:
@[]
(the array constructor[]
in conjunction with the array to sequence operator@
) - space alloc:
proc system.newSeq[T](s: var seq[T]; len: Natural)
proc system.newSeq[T](len = 0.Natural): seq[T]
newSeq
creates a new sequence of type seq[T]
with length len
. This is equivalent to s = @[]; setlen(s, len)
, but more efficient since no reallocation is needed.
var
seq_ref0: ref seq[int]
assert seq_ref0.isNil
new(seq_ref0)
assert (not seq_ref0.isNil)
echo repr(seq_ref0)
# --> ref 0x7f180287b050 --> nil
assert seq_ref0[].isNil
var
seq_obj = newSeq[int]()
seq_ref1: ref seq[int]
assert seq_ref1.isNil
new(seq_ref1)
assert (not seq_ref1.isNil)
echo repr(seq_ref1)
# --> ref 0x7f180287b068 --> nil
seq_ref1[] = seq_obj
echo repr(seq_obj)
# --> 0x7f180287c050[]
echo repr(seq_ref1)
# --> ref 0x7f180287b068 --> 0x7f180287c070[]
var
seq_0: seq[int]
seq_1 = @[0, 1, 2]
assert seq_0.isNil
seq_0 = @[0, 1, 2]
assert (not seq_0.isNil)
assert low(seq_0) == 0
assert high(seq_0) == 2
assert len(seq_0) == 3
assert seq_0 & seq_1 == @[0, 1, 2, 0, 1, 2]
assert seq_0.pop() == 2
seq_0.add(1)
assert seq_0 == @[0, 1, 1]
var
seq_2: seq[int]
assert seq_2.isNil
newSeq(seq_2, 0)
assert (not seq_2.isNil)
assert len(seq_2) == 0
var
seq_3 = newSeq[int]()
assert (not seq_3.isNil)
assert len(seq_3) == 0
var
seq_4 = newSeq[int](3)
assert (not seq_4.isNil)
assert len(seq_4) == 3
proc shallow[T](s: var seq[T])
marks a sequence s as shallow. Subsequent assignments will not perform deep copies of s. This is only useful for optimization purposes.
var
s0 = @[0, 0, 0]
var
s1 = s0
shallow(s0)
var
s2 = s0
s3 = s0[0..2]
inc s0[0]
s0.add(3)
assert s0 == @[1, 0, 0, 3]
assert s1 == @[0, 0, 0]
assert s2 == @[1, 0, 0]
assert s3 == @[0, 0, 0]
openArray
{.push boundChecks: off.}
proc to_str_seq(a: openArray[int]): seq[string] =
#var
# result = newSeq[string](len(a))
newSeq(result, len(a))
for i, it in a:
result[i] = $it
{.pop.}
assert to_str_seq([1,2,3]) == @["1", "2", "3"]
{.push boundChecks: off.}
proc to_reversed_seq[T](a: openArray[T]): seq[T] =
var
li = 0
hi = high(a)
newSeq(result, hi + 1)
while hi >= 0:
result[hi] = a[li]
dec(hi)
inc(li)
{.pop.}
var
arr = [1, 2, 3]
s = @[1, 2, 3]
assert arr.to_reversed_seq == @[3, 2, 1]
assert s.to_reversed_seq == @[3, 2, 1]
varargs
proc anyFileWriteln0(f: File, sargs: varargs[string]): void =
for s in sargs:
write(f, s)
write(f, "\n")
anyFileWriteln0(stdout, "abc", "def", "xyz")
# = anyFileWriteln0(stdout, ["abc", "def", "xyz",])
proc anyFileWriteln1(f: File, sargs: varargs[string, `$`]): void =
for s in sargs:
write(f, s)
write(f, "\n")
anyFileWriteln1(stdout, 123, "def", 4.0)
# = anyFileWriteln1(stdout, [$123, "def", $4.0])
proc toS(x): string = $x
proc anyFileWriteln2(f: File, sargs: varargs[string, toS]): void =
for s in sargs:
write(f, s)
write(f, "\n")
anyFileWriteln2(stdout, 123, 456, 5)
# = anyFileWriteln2(stdout, [toS(123), toS(456), toS(5)])
proc takeV[T](x: varargs[T]): void =
for s in x:
echo($s)
takeV([1, 2, 3])
varargs[expr]
is treated specially: It matches a variable list of arguments of arbitrary type but always constructs an implicit array. This is required so that the builtin echo proc
does what is expected:
proc echo*(x: varargs[expr, `$`]) {...}
echo(@[1, 2, 3])
# prints "@[1, 2, 3]" and not "123"
Tuples and object types
tuple
The order of the fields in the constructor must match the order of the tuple's definition. Different tuple-types are equivalent if they specify the same fields of the same type in the same order. The names of the fields also have to be identical.
The assignment operator for tuples copies each component.
type
Person0 = tuple[
name: string,
age: Natural,
]
var
p0: Person0
p0 = (name: "Peter", age: 20.Natural) # assinment op does COPY each component
p0 = ("Peter", 20.Natural)
# tuples in a type section can also be defined with indentation instead of []:
type
Person1 = tuple
name: string
age: Natural
var
p1: Person1 = (name: "Peter", age: 20.Natural)
p2: Person1 = (name: "Adam", age: 30.Natural)
assert p0 == p1
assert p0 != p2
assert p0 == (name: "Peter", age: 20.Natural)
# These are invalid because illegal recursion in the type
# type
# MyTuple0 = tuple[a: MyTuple0]
# MyTuple1 = tuple[a: ref MyTuple1]
import tables
var
field_seq: seq[string] = @[]
field_pairs: Table[string, string] = initTable[string, string]()
for v in p0.fields:
field_seq.add($v)
assert field_seq == @["Peter", "20"]
field_seq.setLen(0)
for v0, v1 in fields(p0, p2):
field_seq.add($v0)
field_seq.add($v1)
assert field_seq == @["Peter", "Adam", "20", "30"]
for k, v in p0.fieldPairs:
field_pairs.add($k, $v)
assert field_pairs == {"name": "Peter", "age": "20"}.toTable
object
The default assignment operator for objects copies each component.
In contrast to tuples, different object types are never equivalent. Objects that have no ancestor are implicitly final and thus have no hidden type field. One can use the inheritable pragma to introduce new object roots apart from system.RootObj.
type
PersonObj* {.inheritable.} = object
name*: string
age*: Natural
Person* = ref PersonObj
SubPersonObj* = object of PersonObj
SubPerson* = ref SubPersonObj
AltPersonObj* {.inheritable.} = object
name*: string
age*: Natural
AltPerson* = ref AltPersonObj
StudentObj* = object of PersonObj
id: int
Student* = ref StudentObj
var
pobj: PersonObj
p0, p1: Person
subp: SubPerson
altp: AltPerson
s: Student
echo(repr(pobj))
# --> [name = nil, age = 0]
assert p0.isNil
p0 = Person(name: "Peter", age: 20.Natural) # assignment op does COPY each component
p1 = Person(name: "Peter", age: 20.Natural)
subp = SubPerson(name: "Peter", age: 20.Natural)
altp = AltPerson(name: "Peter", age: 20.Natural)
s = Student(name: "Peter", age: 20.Natural, id: 1)
assert p0 of Person
assert p0 of PersonObj
assert p0[] of Person
assert p0[] of PersonObj
assert p0.type is Person
assert p0[].type is PersonObj
assert subp of SubPerson
assert subp of SubPersonObj
assert subp[] of SubPerson
assert subp[] of SubPersonObj
assert subp.type is SubPerson
assert subp[].type is SubPersonObj
assert subp of Person
assert subp of PersonObj
assert subp[] of Person
assert subp[] of PersonObj
assert subp.type is Person
assert subp[].type is PersonObj
assert s of Person
assert s of PersonObj
assert s[] of Person
assert s[] of PersonObj
assert s.type is Person
assert s[].type is PersonObj
assert p0 != p1
assert p0[] == p1[]
assert p0 != subp
assert p0[] == subp[]
#assert subp[] != p0[]
# static error: type mismatch: got(SubPersonObj, PersonObj)
#assert p0 != altp
# static error: type mismatch: got(Person, AltPerson)
#assert p0[] == altp[]
# static error: type mismatch: got(Person, AltPerson)
assert p0 != s
#assert s != p0
# static eror: type mismatch: got(Student, Person)
assert p0[] == s[]
#assert s[] == p0[]
# static eror: type mismatch: got(StudentObj, PersonObj)
assert s.name == "Peter"
assert s.age == 20
assert s.id == 1
proc newStudent(name: string, age: int, id: int): Student =
new(result)
result.name = name
result.age = age.Natural
result.id = id
var
s1 = newStudent("Name", 20, 1)
s2 = newStudent("Name", 20, 1)
assert s1 != s2
assert s1[] == s2[]
var
s3: Student
new(s3)
s3.name = "Name"
s3.age = 20
s3.id = 1
import tables
p1.name = "Adam"
p1.age = 30
subp.name = "Adam"
subp.age = 30
var
field_seq: seq[string] = @[]
field_pairs: Table[string, string] = initTable[string, string]()
for v in p0[].fields:
field_seq.add($v)
assert field_seq == @["Peter", "20"]
field_seq.setLen(0)
for v0, v1 in fields(p0[], p1[]):
field_seq.add($v0)
field_seq.add($v1)
assert field_seq == @["Peter", "Adam", "20", "30"]
for k, v in p0[].fieldPairs:
field_pairs.add($k, $v)
assert field_pairs == {"name": "Peter", "age": "20"}.toTable
field_seq.setLen(0)
# static error: type mismatch: got(SubPersonObj) but expected 'PersonObj'
#for v0, v1 in fields(p0[], subp[]):
# field_seq.add($v0)
# field_seq.add($v1)
# static error: type mismatch: got(StudentObj) but expected 'PersonObj'
#for v0, v1 in fields(p0[], s[]):
# field_seq.add($v0)
# field_seq.add($v1)
https://github.com/nim-lang/Nim/issues/3012
https://github.com/nim-lang/Nim/issues/2926
type
A {.inheritable.} = object
B = ref object {. inheritable .}
C {.inheritable.} = ref object
D = object {.inheritable.}
type
AA = ref object of A
BB = ref object of B
#CC = ref object of C
# Error: inheritance only works with non-final objects
DD = ref object of D
Object construction
Objects can also be created with an object construction expression that has the syntax T(fieldA: valueA, fieldB: valueB, ...) where T is an object type or a ref object type:
var
student_obj = StudentObj(name: "Anton", age: 5, id: 3)
student = Student(name: "Anton", age: 5, id: 3)
For a ref object type system.new is invoked implicitly.
Object variants
An advantage to an object hierarchy is that no casting between different object types is needed. Yet, access to invalid object fields raises an exception.
type
NodeKind = enum
nkInt,
nkFloat,
nkString,
nkAdd,
nkSub,
nkIf,
Node = ref NodeObj
NodeObj = object
case kind: NodeKind
of nkInt:
valInt: int
of nkFloat:
valFloat: float
of nkString:
valString: string
of nkAdd, nkSub:
opLeft, opRight: Node
of nkIf:
condition, thenPart, elsePart: Node
var
n = Node(kind: nkIf, condition: nil)
n.thenPart = Node(kind: nkFloat, valFloat: 2.0)
try:
n.valString = ""
except FieldError:
# Error: unhandled exception: valString is not accessible [FieldError]
echo(getCurrentExceptionMsg())
try:
n.kind = nkInt
except FieldError:
# Error: unhandled exception: assignment to discriminant changes object branch [FieldError]
echo(getCurrentExceptionMsg())
var x: Node = Node(
kind: nkAdd,
opLeft: Node(kind: nkInt, valInt: 3),
opRight: Node(kind: nkInt, valInt: 2),
)
x.kind = nkSub
In the example the kind field is called the discriminator: For safety its address cannot be taken and assignments to it are restricted: The new value must not lead to a change of the active object branch. For an object branch switch system.reset has to be used.
Set type
The set's basetype can only be an ordinal type. The reason is that sets are implemented as high performance bit vectors.
type
CharSet = set[char]
var
cs0: CharSet = {'a' .. 'z', 'A' .. 'Z', '0' .. '9'}
cs1: CharSet = {'a'.. 'd'}
cs2: CharSet = {'c'.. 'f'}
cs3: CharSet = {'c', 'd'}
assert cs1 + cs2 == {'a' .. 'f'}
assert cs1 - cs2 == {'a', 'b'}
assert cs1 * cs2 == {'c', 'd'}
assert cs1 != cs2
assert cs1 >= cs3
assert cs1 > cs3
assert 'a' in cs1
assert cs1.contains('a')
assert 'e' notin cs1
assert card(cs1) == 4
incl(cs1, 'a')
assert cs1 == {'a'.. 'd'}
incl(cs1, 'e')
assert cs1 == {'a'.. 'e'}
excl(cs1, 'e')
assert cs1 == {'a'.. 'd'}
operation | meaning |
---|---|
A + B | union of two sets |
A * B | intersection of two sets |
A - B | difference of two sets (A without B's elements) |
A == B | set equality |
A <= B | subset relation (A is subset of B or equal to B) |
A < B | strong subset relation (A is a real subset of B) |
e in A | set membership (A contains element e) |
e notin A | A does not contain element e |
contains(A, e) | A contains element e |
card(A) | the cardinality of A (number of elements in A) |
incl(A, elem) | same as A = A + {elem} |
excl(A, elem) | same as A = A - {elem} |
Reference and pointer type
The .
(access a tuple/object field operator) and [] (array/string/sequence index operator) operators perform implicit dereferencing operations for reference types:
To allocate a new traced object, the built-in procedure new
has to be used. To deal with untraced memory, the procedures alloc
, dealloc
and realloc
can be used.
Special care has to be taken if an untraced object contains traced objects like traced references, strings or sequences: in order to free everything properly, the built-in procedure GCunref has to be called before freeing the untraced memory manually:
If a reference points to nothing, it has the value nil.
type
Node = ref NodeObj
NodeObj = object
left, right: Node
data: int
var
x: Node
y: Node
z: Node
new(x)
new(y)
new(z)
x.data = 9
x.left = y
x.right = z
proc get_data(x: NodeObj): int =
return x.data
# static error
#echo(get_data(x))
assert get_data(x[]) == 9
{. experimental .}
assert get_data(x) == 9
type
# the object type is anonymous
Node = ref object
left, right: Node
data: int
var
n: Node
new(n)
echo(repr(n))
# --> ref 0x7f9e92e4c050 --> [left = nil, right = nil, data = 0]
echo(repr(n[]))
# --> [left = nil, right = nil, data = 0]
type
Data = tuple[
x, y: int,
s: string,
]
# allocate memory for Data on the heap
var
# Without the GCunref call the memory allocated for the d.s string
# would never be freed. The example also demonstrates two important
# features for low level programming: the sizeof proc returns the
# size of a type or value in bytes.
# The cast operator can circumvent the type system: the compiler is
# forced to treat the result of the alloc0 call (which returns an
# untyped pointer) as if it would have the type ptr Data.
# Casting should only be done if it is unavoidable: it breaks type
# safety and bugs can lead to mysterious crashes.
# Note: The example only works because the memory is initialized to
# zero (alloc0 instead of alloc does this): d.s is thus initialized
# to nil which the string assignment can handle.
# One needs to know low level details like this when mixing garbage
# collected data with unmanaged memory.
d = cast[ptr Data](alloc0(sizeof(Data)))
# create a new string on the garbage collected heap
d.s = "abc"
# tell the GC that the string is not needed anymore
GCunref(d.s)
# free the memory
dealloc(d)
type
RequestObj {.inheritable.} = object
body: string
Request = ref RequestObj
proc handle_varobj(r: var RequestObj) =
r.body = "changed by handle_varobj"
proc handle_varobj_returning(r: var RequestObj): RequestObj =
r.body = "changed by handle_varobj_returning"
return r
proc handle_ref(r: Request) =
r.body = "changed by handle_ref"
var
obj0: RequestObj = RequestObj(body: "original")
obj1: RequestObj = RequestObj(body: "original")
assert obj0 == obj1
assert obj0.addr != obj1.addr
obj1 = obj0
assert obj0 == obj1
assert obj0.addr != obj1.addr
obj1.body = "x"
assert obj0 != obj1
assert obj0.body != obj1.body
handle_varobj(obj0)
assert obj0.body == "changed by handle_varobj"
obj1 = handle_varobj_returning(obj0)
assert obj0.body == obj1.body
assert obj0 == obj1
assert obj0.addr != obj1.addr
var
ref0: Request
ref1: Request
new(ref0)
new(ref1)
assert ref0 != ref1
assert ref0[] == ref1[]
ref1 = ref0
ref0.body = "original"
assert ref0.body == ref1.body
assert ref0 == ref1
assert ref0.addr != ref1.addr
assert ref0[] == ref1[]
assert ref0[].addr == ref1[].addr
type
Obj = object
kind: string
ObjRef = ref Obj
ObjPtr = ptr Obj
var
obj = Obj(kind: "Obj")
obj_ref, obj_ref1 : ObjRef
obj_ptr, obj_ptr1 : ObjPtr
new(obj_ref)
obj_ref.kind = "ObjRef"
obj_ref1 = ObjRef(kind: "ObjRef")
obj_ptr = cast[ptr Obj](alloc0(sizeof(Obj)))
obj_ptr.kind = "ObjPtr"
obj_ptr1 = create(Obj)
obj_ptr1.kind = "ObjPtr"
echo("======== object ========")
echo("obj = ", obj)
# --> (kind: Obj)
echo("obj = ", repr obj)
# --> [kind = 0x7f3ee921f050"Obj"]
echo("obj.addr = ", repr obj.addr)
# --> ref 0x624170 --> [kind = 0x7f3ee921f050"Obj"]
echo("obj.addr.pointer = ", repr obj.addr.pointer)
# --> 0x624170
assert obj.addr[] == obj
assert cast[ObjPtr](obj.addr.pointer)[] == obj
assert cast[ptr Obj](obj.addr.pointer)[] == obj
echo("======== ref ========")
echo("obj_ref = ", repr obj_ref)
# --> ref 0x7f5d71971050 --> [kind = 0x7f5d71970078"ObjRef"]
echo("obj_ref.addr = ", repr obj_ref.addr)
# --> ref 0x624168 --> ref 0x7f5d71971050 --> [kind = 0x7f5d71970078"ObjRef"]
echo("obj_ref.addr.pointer = ", repr obj_ref.addr.pointer)
# --> 0x624168
assert obj_ref.addr[] == obj_ref
assert obj_ref.addr[][] == obj_ref[]
assert cast[ptr ref Obj](obj_ref.addr)[] == obj_ref
assert cast[ptr ref Obj](obj_ref.addr)[][] == obj_ref[]
echo("======== ptr ========")
echo("obj_ptr = ", repr(obj_ptr))
# ref 0x7f5d71971080 --> [kind = 0x7f5d719700c8"ObjPtr"]
echo("obj_ptr.pointer = ", repr(obj_ptr.pointer))
# 0x7f5d71971080
assert cast[ptr Obj](obj_ptr.pointer) == obj_ptr
assert cast[ptr Obj](obj_ptr.pointer)[] == obj_ptr[]
proc check(x: pointer): string =
"pointer"
proc check(x: ptr int): string =
"ptr int"
assert check(create(int)) == "ptr int"
assert check(create(int).pointer) == "pointer"
# `ptr` is implicitly convertible to `pointer`
assert check(create(char)) == "pointer"
proc check(x: ptr): string =
"ptr"
assert check(create(char)) == "ptr"
GCunref(obj_ptr.kind)
dealloc(obj_ptr)
GCunref(obj_ptr1.kind)
dealloc(obj_ptr1)
not nil annotation
All types for that nil is a valid value can be annotated to exclude nil as a valid value with the not nil annotation:
type
Proc = ref ProcObj not nil
ProcObj = (proc (x, y: int)) not nil
proc call_proc(x: ProcObj): string =
"called"
proc call_proc(x: Proc): string =
"called"
proc deal_str(s: string not nil): string =
"called"
var
proc_obj: ProcObj
proc_ref: Proc
s: string
# static error
#call_proc(nil)
assert proc_obj.isNil
assert call_proc(proc_obj) == "called"
assert proc_ref.isNil
assert call_proc(proc_ref) == "called"
# static error
#deal_str(nil)
assert s.isNil
#discard deal_str(s)
# Error: cannot prove 's' is not null
Memory regions
Procedural type
type
Event {.pure.} = enum
starting,
started,
stopping,
stopped,
restarting,
restarted,
proc callback_event(
e: Event,
callback: proc(e: Event),
) =
callback(e)
proc callback_event_varargs(
e: Event,
callbacks: varargs[proc(e: Event) {.nimcall.}],
) =
for c in callbacks:
c(e)
proc callback_event_openArray(
e: Event,
callbacks: openArray[proc(e: Event) {.nimcall.}],
) =
for c in callbacks:
c(e)
proc echo_callback0(
e: Event,
) =
echo("Event: ", e)
proc echo_callback1(
e: Event,
): void {.nimcall.} =
echo("Event: ", e)
callback_event(Event.started, echo_callback0)
callback_event(Event.started, echo_callback1)
callback_event_varargs(Event.started, echo_callback0, echo_callback1)
callback_event_openArray(Event.started, @[echo_callback0, echo_callback1])
type
Request = ref object
data: string
proc handle_request(
req: var Request,
c: proc(req: var Request),
) =
c(req)
proc addstr_handler(
req: var Request,
): void =
req.data &=" added"
discard
var
req = Request(data: "data")
handle_request(req, addstr_handler)
assert req.data == "data added"
type
Event {.pure.} = enum
starting,
started,
stopping,
stopped,
restarting,
restarted,
proc callback_one(
e: Event,
callback: proc(e: Event),
) =
callback(e)
proc callback_varargs(
e: Event,
callbacks: varargs[proc(e: Event) {.closure.}],
) =
for cb in callbacks:
cb(e)
proc callback_openArray(
e: Event,
callbacks: openArray[proc(e: Event) {.closure.}],
) =
for cb in callbacks:
cb(e)
proc echo_cb0(e: Event) =
echo("Event: ", e)
proc echo_cb1(e: Event) {.closure.} =
echo("Event: ", e)
callback_one(Event.started, echo_cb0)
# static error (different calling convention)
#callback_varargs(Event.started, echo_cb0)
#callback_openArray(Event.started, echo_cb0)
callback_varargs(Event.started, echo_cb1)
callback_openArray(Event.started, [echo_cb1])
callback_openArray(Event.started, @[echo_cb1])
type
Request = ref object
data: string
proc handle_request(
r: var Request,
handler: proc(r: var Request): string
): string =
return handler(r)
proc change_data_handler(
r: var Request,
): string {.procvar.} =
r.data &=" changed"
return r.data
var
r = Request(data: "data")
assert handle_request(r, change_data_handler) == "data changed"
assert r.data == "data changed"
distinct type
type
Dollar* = distinct float
var
d: Dollar
# static error
#echo(d + 12)
proc `$`*(x: Dollar): string =
result = "$" & $float(x)
proc `==`*(x, y: Dollar): bool {.borrow.}
proc `<=`*(x, y: Dollar): bool {.borrow.}
proc `<`*(x, y: Dollar): bool {.borrow.}
proc `+`*(x, y: Dollar): Dollar =
result = Dollar(float(x) + float(y))
proc `-`*(x, y: Dollar): Dollar {.borrow.}
proc `*`*(x: Dollar, y: float): Dollar {.borrow.}
proc `*`*(x: float, y: Dollar): Dollar {.borrow.}
proc `/`*(x: Dollar, y: float): Dollar {.borrow.}
assert Dollar(2) + d == Dollar(2) * 1
template add_same_types(typ: typedesc): stmt =
proc `+`*(x, y: typ): typ {.borrow.}
proc `-`*(x, y: typ): typ {.borrow.}
# unary ops
proc `+`*(x: typ): typ {.borrow.}
proc `-`*(x: typ): typ {.borrow.}
template multiply_by_base_type(typ, base_typ: typedesc): stmt =
proc `*`*(x: typ, y: base_typ): typ {.borrow.}
proc `*`*(x: base_typ, y: typ): typ {.borrow.}
proc `/`*(x: typ, y: base_typ): typ {.borrow.}
template compare_same_types(typ: typedesc): stmt =
proc `==`*(x, y: typ): bool {.borrow.}
proc `<`*(x, y: typ): bool {.borrow.}
proc `<=`*(x, y: typ): bool {.borrow.}
template define_currency(typ, base_typ: expr): stmt =
type
typ* = distinct base_typ
add_same_types(typ)
multiply_by_base_type(typ, base_typ)
compare_same_types(typ)
define_currency(Euro, float)
type
FooObj = object
i: int
s: string
BarObj {.borrow: `.`.} = distinct FooObj
Bar = ref BarObj
var
b: Bar
new(b)
b.i = 1
b.s = "s"
type
AltString = distinct string
proc add *(x: var AltString, y: string) =
## workaround: https://github.com/nim-lang/Nim/issues/3082
system.add(string(x), y)
proc `&=` *(x: var AltString, y: string) =
## workaround: https://github.com/nim-lang/Nim/issues/3082
system.`&=`(string(x), y)
var
a = AltString("altstring")
b = a
assert string(a) == "altstring"
assert string(a) == string(b)
string(a)[0 .. 2] = ""
assert repr(a) != repr(b)
assert repr(a) == repr(string(a))
assert repr(a) == repr(cast[string](a))
type
AltInt = distinct int
var
i = AltInt(1)
int(i) += 1
assert int(i) == 2
assert repr(i) == repr(int(i))
assert repr(i) == repr(cast[int](i))
void type
proc callProc[T: int|void](p: proc (x: T), x: T) =
when T is void:
p()
else:
p(x)
proc intProc(x: int) =
echo("called intProc")
proc emptyProc() = discard
callProc[int](intProc, 12)
callProc(intProc, 12)
callProc[void](emptyProc)
# static error: a void type can not be inferred in generic code.
#callProc(emptyProc)
Statements and Expressions
statement list expression
var str = (var x: seq[string] = @[]; for i in [0, 1, 2]: x.add($i); x)
echo str
assert str == @["0", "1", "2"]
All the other statements than list expression must be of type void. (One can use discard to produce a void type.) (;) does not introduce a new scope.
discard statement
proc p(x, y: int): int {.discardable.} =
x + y
p(1, 2)
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 | castT; this may be an invalid value |
# http://forum.nim-lang.org/t/1429/
# I'm not sure what you are expecting. {.noinit.} makes no guarantees
# about the content of a variable, and for global variables, it will
# generally result in the same initial value, anyway.
# All that {.noinit.} says is that it won't explicitly assign default
# values; the variable will still have some value.
# In the case of global variables, whatever's associated with zeroed
# memory; in the case of local variables, whatever's been on the stack
# location/in the register.
# For me, all the asserts work, and that's pretty much what I'd expect.
var
int_0: int
int_1 {.noInit.}: int
float_0: float
float_1 {.noInit.}: float
char_0: char
char_1 {.noInit.}: char
string_0: string
string_1 {.noInit.}: string
bool_0: bool
bool_1 {.noInit.}: bool
arr_0: array[0..2, int]
arr_1 {.noInit.}: array[0..2, int]
seq_0: seq[int]
seq_1 {.noInit.}: seq[int]
tuple_0: tuple[id: int]
tuple_1 {.noInit.}: tuple[id: int]
range_0: range[0 .. 2]
range_1 {.noInit.}: range[0 .. 2]
proc_0: proc (x: int)
proc_1 {.noInit.}: proc (x: int)
assert int_0 == 0
assert int_1 == 0
assert float_0 == 0.0
assert float_1 == 0.0
assert char_0 == '\x0'
assert char_1 == '\x0'
assert string_0 == nil
assert string_1 == nil
assert bool_0 == false
assert bool_1 == false
assert arr_0 == [0, 0, 0]
assert arr_1 == [0, 0, 0]
assert seq_0 == nil
assert seq_1 == nil
assert tuple_0 == (id: 0)
assert tuple_1 == (id: 0)
assert range_0 == 0
assert range_1 == 0
assert proc_0 == nil
assert proc_1 == nil
type
Obj = object
x: int
y: string
Ref = ref Obj
var
obj_0: Obj
obj_1 {.noInit.}: Obj
ref_0: Ref
ref_1 {.noInit.}: Ref
assert obj_0.x == 0
assert obj_0.y == nil
assert obj_0 == obj_1
assert ref_0 == nil
assert ref_1 == nil
proc procInt: int {.noInit.} =
echo("int: ", result) # => 140732219179232
assert procInt() != 0
proc procFloat: float {.noInit.} =
echo("float: ", result) # => 6.953095475945912e-310
assert procFloat() != 0.0
proc procChar: char {.noInit.} =
echo("char: ", repr(result)) # => '\0'
assert procChar() == '\0'
proc procString: string {.noInit.} =
echo("string: ", result) # => 6.953095475945912e-310
assert procString() != nil
proc procBool: bool {.noInit.} =
echo("bool: ", result) # => false
assert procBool() == false
proc procArray: array[0..2, int] {.noInit.} =
echo("array: ", repr(result)) # => [0, 0, 0]
assert procArray() == [0, 0, 0]
#proc procSeq: seq[int] {.noInit.} =
# echo("seq: ", repr(result))
#assert procSeq() == nil
proc procTuple: (int, int) {.noInit.} =
echo("tuple: ", result) # => (Field0: 3, Field1: 2)
assert procTuple() != (0, 0)
proc procRange: range[0 .. 2] {.noInit.} =
echo("range: ", result) # => 65536
assert procRange() != 0
proc procProc: (proc (x: int): string) {.noInit.} =
echo("proc: ", repr(result)) # => [Field0 = nil, Field1 = nil]
assert procProc() == nil
let statement
let
x: array[0..2, int] = [0, 1, 2]
y: int = 3
# static error: can not take the address
#echo(repr(x.addr))
#echo(repr(y.addr))
tuple unpacking
proc returnTuple: (int, int, int) =
(0, 1, 2)
var
(a, _, c) = returnTuple()
d, e, f = returnTuple()
assert a == 0
assert c == 2
assert d == (0, 1, 2)
assert e == (0, 1, 2)
assert f == (0, 1, 2)
const section
static statement/expression
if statement
A new scope starts for the if/elif condition and ends after the corresponding then block:
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:
# 'm' not declared here
In the example the scopes have been enclosed in {| |}.
case statement
let
i = 1
case i:
of 0 .. 2, 3 .. 4:
echo("0 .. 2, 3 .. 4")
of 5, 6:
echo("5, 6")
of 7 .. 9:
echo("7 .. 9")
else:
discard
let
r: range[0 .. 9] = 3
case r:
of 0 .. 2, 3 .. 4:
echo("0 .. 2, 3 .. 4")
of 5, 6:
echo("5, 6")
of 7 .. 9:
echo("7 .. 9")
const
SymChars: set[char] = {'a' .. 'z', 'A' .. 'Z', '\x80' .. '\xff'}
var
c = 'x'
case c:
of SymChars, '_':
echo("an identifier")
of '0' .. '9':
echo("a number")
else:
echo("other")
type
Direction = enum
north, south, east, west
var
e = north
case e:
of {Direction.low .. Direction(1)}:
echo("north or south")
of east .. west:
echo("east or west")
when statement
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!")
return statement
yield statement
iterator itercount(start, last:int): int =
var i = start
while i <= last:
yield i
inc(i)
for i in itercount(0, 3):
echo(i)
block statement
var
found: bool = false
arr: array[
0 .. 2,
array[0 .. 2, int]
] = [
[0,1,2],
[0,1,2],
[0,1,7],
]
block myblock:
for i in 0 .. 2:
for j in 0 .. 2:
if arr[j][i] == 7:
found = true
break myblock
assert found == true
break statement
var
input: string
while true:
input = readline(stdin)
if input.len > 0:
break
echo(input)
continue statement
while expr1:
stmt1
continue
stmt2
Is equivalent to:
while expr1:
block myBlockName:
stmt1
break myBlockName
stmt2
if expression
var
a = 7
x = if a > 20: 20 elif a > 10: 10 else: 0
y =
if a > 20:
20
elif a > 10:
10
else:
0
z = if a > 20: 20
elif a > 10: 10
else: 0
assert x == 0
assert y == 0
assert z == 0
when expression
var
x = when sizeof(int) == 2: "16bit" elif sizeof(int) == 4: "32bit" elif sizeof(int) == 8: "64bit" else: "unknown"
y =
when sizeof(int) == 2:
"16bit"
elif sizeof(int) == 4:
"32bit"
elif sizeof(int) == 8:
"64bit"
else:
"unknown"
z = when sizeof(int) == 2: "16bit"
elif sizeof(int) == 4: "32bit"
elif sizeof(int) == 8:"64bit"
else: "unknown"
case expression
import strutils
var
animal = "unknown"
x =
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"
y =
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"
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
- 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.
const
x = {"key1": "value1", "key2", "key3": "value2"}
assert x == [("key1", "value1"), ("key2", "value2"), ("key3", "value2")]
import tables
const
s = x.newOrderedTable
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
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.
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:
let s0 = "Hello"
var
s1 = s0
p0 : pointer = addr(s1)
ptr0 : ptr = addr(s1)
ptr1 : ptr string = addr(s1)
assert p0 == ptr0
assert ptr0 == ptr1
echo(repr(p0))
# --> 0x622c58
echo(repr(cast[ptr string](p0)))
# --> ref 0x622c58 --> 0x7f20a4a1d078"Hello"
echo(repr(ptr0))
# --> ref 0x622c58 --> 0x7f20a4a1d078"Hello"
echo(repr(ptr1))
# --> ref 0x622c58 --> 0x7f20a4a1d078"Hello"
echo repr(addr(s1))
# --> ref 0x622c58 --> 0x7f20a4a1d078"Hello"
echo cast[ptr string](p0)[]
# --> Hello
# The following line doesn't compile:
#echo repr(addr(s0))
# Error: expression has no address
procedure
Operators are procedures with a special operator symbol as identifier:
import strutils
proc `$` (x: int): string =
# converts an integer to a string; this is a prefix operator.
result = intToStr(x)
Operators with one parameter are prefix operators, operators with two parameters are infix operators. (However, the parser distinguishes these from the operator's position within an expression.) There is no way to declare postfix operators: all postfix operators are built-in and handled by the grammar explicitly.
Any operator can be called like an ordinary proc with the 'opr' notation. (Thus an operator can have more than two parameters):
proc `*+` (a, b, c: int): int =
# Multiply and add
result = a * b + c
assert `*+`(3, 4, 6) == `+`(`*`(3, 4), 6)
export marker
proc exportedEcho*(s: string) = echo s
proc `*`*(a: string; b: int): string =
result = newStringOfCap(a.len * b)
for i in 1..b:
result.add a
assert "ABC" * 3 == "ABCABCABC"
var
exportedVar*: int
const
exportedConst* = 78
type
ExportedType* = object
exportedField*: int
method call syntax
import strutils
echo("abc".len)
# is the same as echo(len("abc"))
echo("abc".toUpper())
echo({'a', 'b', 'c'}.card)
stdout.writeln("Hallo")
# the same as writeln(stdout, "Hallo")
properties
type
Socket* = ref object of RootObj
FHost: int # cannot be accessed from the outside of the module
# the `F` prefix is a convention to avoid clashes since
# the accessors are named `host`
proc host*(s: Socket): int {.inline.} =
## getter of hostAddr
s.FHost
proc `host=`*(s: var Socket, value: int) {.inline.} =
## setter of hostAddr
s.FHost = value
var
s: Socket
new(s)
s.host = 34
# same as `host=`(s, 34)
assert s.host == 34
closure
proc create_closure_seq(count: Positive): seq[proc: int] =
newSeq(result, count)
#for i in 0 .. count - 1:
for i in countup(0, count - 1):
result[i] = proc: int = int(i)
var
result = (var a: array[0..2, int]; for i, p in create_closure_seq(3).pairs: a[i] = p(); a)
assert result == [2, 2, 2]
anonymous procedure
import algorithm
var
cities = @["Frankfurt", "Tokyo", "New York"]
cities.sort(proc (x,y: string): int =
cmp(x.len, y.len))
assert cities == @["Tokyo", "New York", "Frankfurt"]
nonoverloadable builtins
declared, defined, definedInScope, compiles, low, high, sizeOf, is, of, shallowCopy, getAst, astToStr, spawn, procCall
Thus they act more like keywords than like ordinary identifiers; unlike a keyword however, a redefinition may shadow the definition in the system module. From this list the following should not be written in dot notation x.f since x cannot be type checked before it gets passed to f:
declared, defined, definedInScope, compiles, getAst, astToStr
var parameters
proc divmod(a, b: int; res, remainder: var int) =
res = a div b
remainder = a mod b
var
x, y: int
divmod(8, 5, x, y) # modifies x and y
assert x == 1
assert y == 3
The argument passed to a var parameter has to be an l-value. Var parameters are implemented as hidden pointers.
The above example is equivalent to:
proc divmod(a, b: int; res, remainder: ptr int) =
res[] = a div b
remainder[] = a mod b
var
x, y: int
divmod(8, 5, addr(x), addr(y))
assert x == 1
assert y == 3
This can be done in a cleaner way by returning a tuple.
proc divmod(a, b: int): tuple[res, remainder: int] =
result.res = a div b
result.remainder = a mod b
var
t = divmod(8, 5)
assert t.res == 1
assert t.remainder == 3
var
(res, remainder) = divmod(8, 5)
assert res == 1
assert remainder == 3
Note: var parameters are never necessary for efficient parameter passing. Since non-var parameters cannot be modified the compiler is always free to pass arguments by reference if it considers it can speed up execution.
var return type
A proc, converter or iterator may return a var type which means that the returned value is an l-value and can be modified by the caller:
var g = 0
proc WriteAccessToG(): var int =
result = g
WriteAccessToG() = 6
assert g == 6
It is a compile time error if the implicitly introduced pointer could be used to access a location beyond its lifetime:
# static error: address of 'g' may not escape its stack frame
proc WriteAccessToG(): var int =
var g = 0
result = g # Error!
For iterators, a component of a tuple return type can have a var type too:
var
sq = @["a", "b", "c"]
# static error: 'str[0]' cannot be assigned to
#for i, str in sq.pairs:
# str[0] = 'x'
for i, str in sq.mpairs:
str[0] = 'x'
assert sq == @["x", "x", "x"]
# same iterator as built-in mpairs
iterator custom_mpairs(a: var seq[string]): tuple[key: int, val: var string] =
for i in 0..a.high:
yield (i, a[i])
for i, str in sq.custom_mpairs:
str.add("y")
assert sq == @["xy", "xy", "xy"]
var
arr: array[
0..2,
array[0..2, int],
] = [
[0, 1, 2],
[0, 1, 2],
[0, 1, 2],
]
# static error: 'item[0]' cannot be assigned to
#for item in arr.items:
# item[0] = 2
for item in arr.mitems:
item[0] = 2
assert arr[0] == [2, 1, 2]
In the standard library every name of a routine that returns a var type starts with the prefix m per convention.
Overloading of the subscript operator
The [] subscript operator for arrays/openarrays/sequences can be overloaded.
Multi-methods
Procedures always use static dispatch. Multi-methods use dynamic dispatch.
type
Expression = ref object of RootObj ## abstract base class for an expression
Literal = ref object of Expression
x: int
PlusExpr = ref object of Expression
a, b: Expression
method eval(e: Expression): int =
# override this base method
quit "to override!"
method eval(e: Literal): int = return e.x
method eval(e: PlusExpr): int =
# watch out: relies on dynamic binding
result = eval(e.a) + eval(e.b)
proc newLit(x: int): Literal =
new(result)
result.x = x
proc newPlus(a, b: Expression): PlusExpr =
new(result)
result.a = a
result.b = b
assert eval(
newPlus(
newPlus(
newLit(1), newLit(2)
),
newLit(4)
)
) == 7
type
Thing = ref object of RootObj
Unit = ref object of Thing
x: int
method collide(a, b: Thing): int {.inline.} =
quit "to override!"
method collide(a: Thing, b: Unit): int {.inline.} =
return 1
method collide(a: Unit, b: Thing): int {.inline.} =
return 2
var a, b: Unit
new a
new b
assert collide(a, b) == 2
Invocation of a multi-method cannot be ambiguous: collide 2 is preferred over collide 1 because the resolution works from left to right. In the example (Unit, Thing)
is preferred over (Thing, Unit)
.
Performance note: Nim does not produce a virtual method table, but generates dispatch trees. This avoids the expensive indirect branch for method calls and enables inlining. However, other optimizations like compile time evaluation or dead code elimination do not work with methods.
import math
type
Dollar* = distinct float
Kg* = distinct float
Fruit* = ref object {.inheritable.}
origin*: string
price*: Dollar
Banana* = ref object of Fruit
size*: int
Pumpkin* = ref object of Fruit
weight*: Kg
BigPumpkin* = ref object of Pumpkin
Bucket* {.inheritable.} = ref object
fruits*: seq[string]
# op proc
proc `$` *(x: Dollar): string {.borrow.}
proc `$` *(x: Kg): string {.borrow.}
proc `==` *(x, y: Dollar): bool {.borrow, inline.}
# op method
method `$`*(self: Fruit): string =
"Fruit: origin='" & self.origin & ", price=" & $self.price
method `$`*(self: Banana): string =
result = procCall($Fruit(self)) & ", size=" & $self.size
result[0 .. 4] = "Banana"
method `$`*(self: Pumpkin): string =
result = procCall($Fruit(self)) & ", weight=" & $self.weight
result[0 .. 4] = "Pumpkin"
method `$`*(self: BigPumpkin): string =
result = procCall($Fruit(self)) & ", weight=" & $self.weight
result[0 .. 4] = "BigPumpkin"
# reduction method
method reduction_m(self: Fruit): Dollar =
echo(
"Called Fruit.reduction_m (" & $self & ")"
)
Dollar(0)
method reduction_m(self: Banana): Dollar =
echo(
"Called Banana.reduction_m (" & $self & ")"
)
Dollar(9)
method reduction_m(self: Pumpkin): Dollar =
echo(
"Called Pumpkin.reduction_m (" & $self & ")"
)
Dollar(1)
# reduction proc
proc reduction_p(self: Fruit): Dollar =
echo(
"Called Fruit.reduction_p (" & $self & ")"
)
Dollar(0)
proc reduction_p(self: Banana): Dollar =
echo(
"Called Banana.reduction_p (" & $self & ")"
)
Dollar(9)
proc reduction_p(self: Pumpkin): Dollar =
echo(
"Called Pumpkin.reduction_p (" & $self & ")"
)
Dollar(1)
# calcPrice method
method calcPrice_m*(self: Fruit): Dollar =
echo(
"Called Fruit.calcPrice_m (" & $self & ")"
)
Dollar(round(float(self.price) * 100) / 100 - float(self.reduction_m))
method calcPrice_m*(self: Banana): Dollar =
echo(
"Called Banana.calcPrice_m (" & $self & ")"
)
procCall Fruit(self).calcPrice_m()
method calcPrice_m*(self: Pumpkin): Dollar =
echo(
"Called Pumpkin.calcPrice_m (" & $self & ")"
)
Dollar(float(procCall(Fruit(self).calcPrice_m())) * float(self.weight))
method calcPrice_m*(self: Bigpumpkin): Dollar =
echo(
"Called Bigpumpkin.calcPrice_m (" & $self & ")"
)
Dollar(1000)
# calcPrice proc
proc calcPrice_p*(self: Fruit): Dollar =
echo(
"Called Fruit.calcPrice_p (" & $self & ")"
)
Dollar(round(float(self.price) * 100) / 100 - float(self.reduction_p))
proc calcPrice_p*(self: Banana): Dollar =
echo(
"Called Banana.calcPrice_p (" & $self & ")"
)
calcPrice_p(Fruit(self))
proc calcPrice_p*(self: Pumpkin): Dollar =
echo(
"Called Pumpkin.calcPrice_p (" & $self & ")"
)
Dollar(float(calcPrice_p(Fruit(self))) * float(self.weight))
proc calcPrice_p*(self: Bigpumpkin): Dollar =
echo(
"Called Bigpumpkin.calcPrice_p (" & $self & ")"
)
Dollar(1000)
# add method
method add(self: Bucket, fruit: Fruit) =
echo(
"Called Bucket.Add (" & $fruit & ")"
)
self.fruits.add($fruit)
method add(self: Bucket, fruit: Banana) =
echo(
"Called Bucket.Add (" & $fruit & ")"
)
procCall self.add(Fruit(fruit))
method add(self: Bucket, fruit: Pumpkin) =
echo(
"Called Bucket.add (" & $fruit & ")"
)
procCall self.add(Fruit(fruit))
# Constructor
proc newBanana*(size: int, origin: string, price: float): Banana =
new(result)
result.origin = origin
result.price = Dollar(price)
result.size = size
proc newPumpkin*(weight: float, origin: string, price: float): Pumpkin =
new(result)
result.origin = origin
result.price = Dollar(price)
result.weight = Kg(weight)
proc newBigPumpkin*(weight: float, origin: string, price: float): BigPumpkin =
new(result)
result.origin = origin
result.price = Dollar(price)
result.weight = Kg(weight)
proc newBucket(): Bucket =
new(result)
result.fruits = newSeq[string]()
if isMainModule:
var banana = newBanana(size=10, origin="country_0", price=1000)
var pumpkin = newPumpkin(weight=100, origin="country_1", price=10000)
var big_pumpkin = newBigPumpkin(weight=1000, origin="country_1", price=20000)
assert banana.calcPrice_m() == 991.0.Dollar
# --> Called Banana.calcPrice_m (Banana: origin='country_0, price=1000.0, size=10)
# --> Called Fruit.calcPrice_m (Banana: origin='country_0, price=1000.0, size=10)
# --> Called Banana.reduction_m (Banana: origin='country_0, price=1000.0, size=10)
assert banana.calcPrice_p() == 1000.0.Dollar
# --> Called Banana.calcPrice_p (Banana: origin='country_0, price=1000.0, size=10)
# --> Called Fruit.calcPrice_p (Banana: origin='country_0, price=1000.0, size=10)
# --> Called Fruit.reduction_p (Banana: origin='country_0, price=1000.0, size=10)
assert pumpkin.calcPrice_m() == 999900.0.Dollar
# --> Called Pumpkin.calcPrice_m (Pumpkin: origin='country_1, price=10000.0, weight=100.0)
# --> Called Fruit.calcPrice_m (Pumpkin: origin='country_1, price=10000.0, weight=100.0)
# --> Called Pumpkin.reduction_m (Pumpkin: origin='country_1, price=10000.0, weight=100.0)
assert pumpkin.calcPrice_p() == 1000000.0.Dollar
# --> Called Pumpkin.calcPrice_p (Pumpkin: origin='country_1, price=10000.0, weight=100.0)
# --> Called Fruit.calcPrice_p (Pumpkin: origin='country_1, price=10000.0, weight=100.0)
# --> Called Fruit.reduction_p (Pumpkin: origin='country_1, price=10000.0, weight=100.0)
var bucket = newBucket()
bucket.add(banana)
# --> Called Bucket.Add (Banana: origin='country_0, price=1000.0, size=10)
# --> Called Bucket.Add (Banana: origin='country_0, price=1000.0, size=10)
bucket.add(pumpkin)
# --> Called Bucket.add (Pumpkin: origin='country_1, price=10000.0, weight=100.0)
# --> Called Bucket.Add (Pumpkin: origin='country_1, price=10000.0, weight=100.0)
bucket.add(big_pumpkin)
# --> Called Bucket.add (BigPumpkin: origin='country_1, price=20000.0, weight=1000.0)
# --> Called Bucket.Add (BigPumpkin: origin='country_1, price=20000.0, weight=1000.0)
echo($bucket.fruits)
# --> @[Banana: origin='country_0, price=1000.0, size=10, Pumpkin: origin='country_1, price=10000.0, weight=100.0, BigPumpkin: origin='country_1, price=20000.0, weight=1000.0]
iterators and for statement
iterator triples *(s: string): (Natural, int, char) {.inline.} =
var i = 0
while i < len(s):
yield (i.Natural, ord(s[i]), s[i])
inc(i)
var q = newSeq[(int, char)](2)
for i, o, c in "hi".triples:
q[i] = (o, c)
assert q == @[(104, 'h'), (105, 'i')]
implicit items/pairs invocation
first class iterators
- inline iterator
Inline iterators are second class citizens; They can be passed as parameters only to other inlining code facilities like templates, macros and other inline iterators. - closure iterator
Can be passed around more freely.
restrictions:
-
yield
in a closure iterator can not occur in atry
statement. - For now, a closure iterator cannot be evaluated at compile time.
-
return
is allowed in a closure iterator (but rarely useful). - Both inline and closure iterators cannot be recursive.
Iterators that are neither marked {.closure.} nor {.inline.} explicitly default to being inline, but that this may change in future versions of the implementation.
The iterator type is always of the calling convention closure implicitly; the following example shows how to use iterators to implement a collaborative tasking system:
iterator count0(): int {.closure.} =
yield 0
iterator count2(): int {.closure.} =
var x = 1
yield x
inc x
yield x
proc invoke(iter: iterator(): int {.closure.}): seq[int] =
newSeq(result, 0)
for x in iter():
result.add(x)
assert invoke(count0) == @[0]
assert invoke(count2) == @[1, 2]
import sequtils
type
Task = iterator (ticker: int)
iterator a1(ticker: int) {.closure.} =
echo "a1: A"
yield
echo "a1: B"
yield
echo "a1: C"
iterator a2(ticker: int) {.closure.} =
echo "a2: A"
yield
echo "a2: B"
yield
echo "a2: C"
yield
echo "a2: D"
yield
echo "a2: E"
yield
echo "a2: F"
iterator a3(ticker: int) {.closure.} =
echo "a3: A"
yield
echo "a3: B"
yield
echo "a3: C"
proc runTasks(tasks: varargs[Task]) =
var
task_seq: seq[Task] = toSeq(tasks.items)
ticker = 0
while true:
let
x = task_seq[ticker]
x(ticker)
if finished(x):
task_seq.delete(ticker)
if task_seq.len == 0:
break
elif ticker >= task_seq.high:
ticker = 0
else:
ticker = (ticker + 1) mod task_seq.len
runTasks(a1, a2, a3)
The builtin system.finished can be used to determine if an iterator has finished its operation; no exception is raised on an attempt to invoke an iterator that has already finished its work.
Note that system.finished is error prone to use because it only returns true one iteration after the iterator has finished:
iterator mycount(a, b: int): int {.closure.} =
var x = a
while x <= b:
yield x
inc x
var
c = mycount # instantiate the iterator
result: seq[int] = @[]
while not finished(c):
result.add(c(1, 3))
assert result == @[1, 2, 3, 0]
# The last '0' is not intended value.
Instead this code has be used;
iterator mycount(a, b: int): int {.closure.} =
var x = a
while x <= b:
yield x
inc x
var
c = mycount # instantiate the iterator
result: seq[int] = @[]
while true:
let value = c(1, 3)
if finished(c):
break # and discard 'value'!
result.add(value)
assert result == @[1, 2, 3]
It helps to think that the iterator actually returns a pair (value, done) and finished is used to access the hidden done field.
Closure iterators are resumable functions and so one has to provide the arguments to every call. To get around this limitation one can capture parameters of an outer factory proc:
proc mycount(a, b: int): iterator: (int, int) =
result = iterator: (int, int) =
var x = a
while x <= b:
yield (x, x)
inc(x)
var
result: seq[int] = @[]
let
c = mycount(1, 3)
for a, b in c():
result.add(a)
assert result == @[1, 2, 3]
converters
Type sections
type # example demonstrating mutually recursive types
Node = ref NodeObj # a traced pointer to a NodeObj
NodeObj = object
le, ri: Node # left and right subtrees
sym: ref Sym # leaves contain a reference to a Sym
Sym = object # a symbol
name: string # the symbol's name
line: int # the line the symbol was declared in
code: Node # the symbol's abstract syntax tree
# http://forum.nim-lang.org/t/1422
type
Obj = object
x: int
y: int
Ref = ref Obj
RefOrObj = Ref or Obj
proc init(arg: var RefOrObj, x, y: int) =
when arg is ref:
new(arg)
arg.x = x
arg.y = y
proc sum(arg: RefOrObj): int =
arg.x + arg.y
proc main() =
var
o: Obj
r: Ref
init(o, 1, 1)
assert sum(o) == 2
init(r, 2, 2)
assert sum(r) == 4
main()
A type section begins with the type keyword. It contains multiple type definitions. A type definition binds a type to a name. Type definitions can be recursive or even mutually recursive. Mutually recursive types are only possible within a single type section. Nominal types like objects or enums can only be defined in a type section.
Exception handling
try statement
import strutils
var
f: File
if open(f, "/root/.bashrc"):
try:
var a = readLine(f)
var b = readLine(f)
echo("sum: " & $(parseInt(a) + parseInt(b)))
except Exception:
echo("Catch all !")
let
exc = getCurrentException()
desc = ": parent=" & repr(exc.parent) & ", msg=" & exc.msg
if exc of ValueError:
# could not convert string to integer
echo("could not convert string to integer. ValueError", desc)
elif exc of OverflowError:
echo("overflow!. OverflowError", desc)
elif exc of IOError:
echo("IO error!. IOError", desc)
else:
echo("Unknown exception!: Exception", desc)
# no-op
except OverflowError:
echo("overflow!")
except ValueError:
echo("could not convert string to integer")
except IOError:
echo("IO error!")
except:
echo("Unknown exception!")
finally:
close(f)
Try expression
import strutils
let
x = try: parseInt("133a")
except: -1
finally: echo "parseInt finished"
assert x == -1
let
y =
try:
parseint("133a")
except:
-1
finally:
echo "parseInt finished"
assert y == -1
To prevent confusing code there is a parsing limitation; if the try follows a ( it has to be written as a one liner
import strutils
let
x = (try: parseInt("133a") except: -1 finally: echo "parseInt finished")
assert x == -1
Except clauses
Note that getCurrentException always returns a ref Exception type. If a variable of the proper type is needed (in the example above, IOError), one must convert it explicitly:
import strutils
var
x: int
try:
x = parseInt("133a")
except ValueError:
let
ref_exc = getCurrentException()
exc = (ref ValueError)(getCurrentException())
echo(repr(ref_exc))
echo(repr(exc))
assert ref_exc == exc
try:
x = parseInt("133a")
except ValueError:
echo("ValueError: ", getCurrentExceptionMsg())
Defer statement
Instead of a try finally statement a defer statement can be used.
Any statements following the defer in the current block will be considered to be in an implicit try block:
Top level defer statements are not supported since it's unclear what such a statement should refer to.
import streams
proc withDefer() =
var
f: File
if f.open(filename="/tmp/numbers.txt", mode=FileMode.fmWrite):
defer:
close(f)
f.write("abc")
f.write("abc")
f.write("\n")
withDefer()
proc sameAsDefer() =
var
f: File
if f.open(filename="/tmp/numbers.txt", mode=FileMode.fmWrite):
try:
f.write("abc")
f.write("def")
f.write("\n")
finally:
f.close()
sameAsDefer()
Raise statement
The raise statement is the only way to raise an exception.
If no exception name is given, the current exception is re-raised. The ReraiseError exception is raised if there is no exception to re-raise. It follows that the raise statement always raises an exception (unless a raise hook has been provided).
raise newException(OSError, "OS error")
Effect system
Exception tracking
The raises pragma can be used to explicitly define which exceptions a proc/iterator/method/converter is allowed to raise. The compiler verifies this.
An empty raises list (raises: []) means that no exception may be raised.
proc p(what: bool) {.raises: [IOError, OSError].} =
if what:
raise newException(IOError, "IO")
else:
raise newException(OSError, "OS")
proc p(): bool {.raises: [].} =
try:
result = true
except:
result = false
A raises list can also be attached to a proc type. This affects type compatibility:
type
Callback = proc (s: string) {.
raises: [IOError],
.}
var
c: Callback
proc p(x: string) =
raise newException(OSError, "OS")
# static error: type error
c = p
For a routine p the compiler uses inference rules to determine the set of possibly raised exceptions; the algorithm operates on p's call graph:
- Every indirect call via some proc type T is assumed to raise system.Exception (the base type of the exception hierarchy) and thus any exception unless T has an explicit raises list. However if the call is of the form f(...) where f is a parameter of the currently analysed routine it is ignored. The call is optimistically assumed to have no effect. Rule 2 compensates for this case.
- Every expression of some proc type wihtin a call that is not a call itself (and not nil) is assumed to be called indirectly somehow and thus its raises list is added to p's raises list.
- Every call to a proc q which has an unknown body (due to a forward declaration or an importc pragma) is assumed to raise system.Exception unless q has an explicit raises list.
- Every call to a method m is assumed to raise system.Exception unless m has an explicit raises list.
- For every other call the analysis can determine an exact raises list.
- For determining a raises list, the raise and try statements of p are taken into consideration.
Rules 1-2 ensure the following works:
proc noRaise(x: proc()) {.raises: [].} =
# unknown call that might raise anything, but valid:
x()
proc doRaise() {.raises: [IOError].} =
raise newException(IOError, "IO")
proc use() {.raises: [].} =
# doesn't compile! Can raise IOError!
noRaise(doRaise)
So in many cases a callback does not cause the compiler to be overly conservative in its effect analysis.
Tag tracking
The exception tracking is part of Nim's effect system. Raising an exception is an effect. Other effects can also be defined. A user defined effect is a means to tag a routine and to perform checks against this tag:
type
IO = object ## input/output effect
proc readLine(): string {.tags: [IO].} =
"some line"
proc IO_please() {.tags: [IO].} =
let
x = readLine()
proc no_IO_please() {.tags: [].} =
# the compiler prevents this:
let
x = readLine()
A tag has to be a type name. A tags list - like a raises list - can also be attached to a proc type. This affects type compatibility.
The inference for tag tracking is analogous to the inference for exception tracking.
Read/Write tracking
Note: Read/write tracking is not yet implemented!
The inference for read/write tracking is analogous to the inference for exception tracking.
Effects pragma
The effects pragma has been designed to assist the programmer with the effects analysis. It is a statement that makes the compiler output all inferred effects up to the effects's position:
proc proc0(what: bool) =
if what:
raise newException(IOError, "IO")
{.effects.}
# --> Hint: ref IOError [User]
else:
raise newException(OSError, "OS")
proc proc1(what: bool) =
if what:
raise newException(IOError, "IO")
else:
raise newException(OSError, "OS")
{.effects.}
# --> Hint: ref IOError [User]
# --> Hint: ref OSError [User]
The compiler produces a hint message that IOError can be raised. OSError is not listed as it cannot be raised in the branch the effects pragma appears in.
Generics
type
BinaryTreeObj[T] = object
## BinaryTreeObj is a generic type with generic param ``T``
left, right: BinaryTree[T]
## left and right subtrees; may be nil
data: T
## the data stored in a node
BinaryTree[T] = ref BinaryTreeObj[T]
## a shorthand for notational convenience
proc newNode[T](data: T): BinaryTree[T] =
## constructor for a node
new(result)
result.data = data
proc add[T](
root: var BinaryTree[T],
n: BinaryTree[T],
) =
if root == nil:
root = n
else:
var
it = root
while it != nil:
var
c = cmp(it.data, n.data)
## compare the data items;
## uses the generic ``cmp`` proc that works for any type that
## has a ``==`` and ``<`` operator
if c < 0:
if it.left == nil:
it.left = n
return
it = it.left
else:
if it.right == nil:
it.right = n
return
it = it.right
iterator inorder[T](root: BinaryTree[T]): T =
## inorder traversal of a binary tree
## recursive iterators are not yet implemented, so this does not
## work in the current compiler!
if root.left != nil:
yield inorder(root.left)
if root.right != nil:
yield inorder(root.right)
var
root: BinaryTree[string]
# instantiates a BinaryTree with the type string
root.add(newNode("hallo"))
root.add(newNode("world"))
# instantiates generic procs ``newNode`` and ``add``
for str in inorder(root):
echo(str)
is operator
type
Table[Key, Val] = object
keys: seq[Key]
values: seq[Val]
count: Natural
when not (Key is string): # nil value for strings used for optimization
deletedKeys: seq[bool]
proc initTable*[Key, Val](initialSize: Natural = 64): Table[Key, Val] =
result.count = 0
newSeq(result.keys, initialSize)
newSeq(result.values, initialSize)
echo("String init")
#newSeq(result.deletedKeys, 0)
proc add*[Key, Val](t: var Table[Key, Val], key: Key, value: Val) =
t.keys[t.count] = key
t.values[t.count] = value
t.count += 1
var
t: Table[string, int] = initTable[string, int]()
t.add("A", 1)
type operator
var
x = 0
var
y: type(x)
If type is used to determine the result type of a proc/iterator/converter call c(X) (where X stands for a possibly empty list of arguments), the interpretation where c is an iterator is preferred over the other interpretations:
import strutils
# strutils contains some ``split`` procs and iterators.
# proc split(s: string; seps: set[char] = Whitespace): seq[string]
# proc split(s: string; sep: char): seq[string]
# proc split(s: string; sep: string): seq[string]
# iterator split(s: string; seps: set[char] = Whitespace): string
# iterator split(s: string; sep: char): string
# iterator split(s: string; sep: string): string
# But since an iterator is the preferred interpretation, `y' has the
# type ``string``:
var
y: type("a b c".split)
y = "a"
Type classes
A type class is a special pseudo-type that can be used to match against types in the context of overload resolution or the is operator. Nim supports the following built-in type classes:
type class | matches |
---|---|
object | any object type |
tuple | any tuple type |
enum | any enumeration |
proc | any proc type |
ref | any ref type |
ptr | any ptr type |
var | any var type |
distinct | any distinct type |
array | any array type |
set | any set type |
seq | any seq type |
auto | any type |
Every generic type automatically creates a type class of the same name that will match any instantiation of the generic type.
Type classes can be combined using the standard boolean operators to form more complex type classes:
# create a type class that will match all tuple and object types
import tables
type
ObjX = object
x: int
y: int
z: int
RefX = ref ObjX
RecordType = tuple[x:int, y:int, z:int] or object
proc printFields(rec: RecordType) =
var
t = initTable[string, int]()
for k, v in fieldPairs(rec):
t[k] = v
assert t == {"x": 1, "y": 2, "z": 3}.toTable
var
tuple_rec: tuple[x:int, y:int, z:int] = (x: 1, y: 2, z: 3)
obj_rec = ObjX(x: 1, y: 2, z: 3)
printFields(tuple_rec)
printFields(obj_rec)
Procedures utilizing type classes in such manner are considered to be implicitly generic. They will be instantiated once for each unique combination of param types used within the program.
Nim also allows for type classes and regular types to be specified as type constraints of the generic type parameter:
proc gproc[T: int|string](x, y: T): string =
"accepted by A"
assert gproc(100, 200) == "accepted by A"
assert gproc("a", "b") == "accepted by A"
# static error: type mismatch
#assert gproc(100, "b") == "accepted by A"
#assert gproc("a", 200) == "accepted by A"
proc gproc[X: int|string, Y: string](x:X, y:Y): string =
"accepted by B"
assert gproc(100, 200) == "accepted by A"
# static error: ambiguous call
#assert gproc("a", "b") == "accepted by A"
assert gproc(100, "b") == "accepted by B"
# static error: type mismatch
assert gproc("a", 200) == "accepted by A"
By default, during overload resolution each named type class will bind to exactly one concrete type. Here is an example taken directly from the system module to illustrate this:
proc `==`*[T: tuple|object](x, y: T): bool =
## generic ``==`` operator for tuples that is lifted from the components
## of `x` and `y`.
for a, b in fields(x, y):
if a != b: return false
return true
Alternatively, the distinct type modifier can be applied to the type class to allow each param matching the type class to bind to a different type.
If a proc param doesn't have a type specified, Nim will use the distinct auto type class (also known as any):
proc concat(a, b): string = $a & $b
assert concat("str+", 1) == "str+1"
Procs written with the implicitly generic style will often need to refer to the type parameters of the matched generic type. They can be easily accessed using the dot syntax:
import typetraits
type
NameEnum = enum
first_name,
last_name,
middle_name
type
NormalMatrix[Rows, Cols, T] = array[Rows, array[Cols, T]]
Normal3Matrix[T] = NormalMatrix[range[0..2], range[0..2], T]
NameEnumMatrix[T] = NormalMatrix[range[0..2], NameEnum, T]
proc `*`* [Rows, Cols, Ta, Tb](
a: NormalMatrix[Rows, Cols, Ta],
b: NormalMatrix[Rows, Cols, Tb],
): NormalMatrix[Rows, Cols, Ta] =
for r in Rows.low .. Rows.high:
var
inner: array[Cols, int]
for c in Cols.low .. Cols.high:
inner[c] = a[r][c] * b[r][c]
result[r] = inner
var
normalm: NormalMatrix[range[0..2], range[0..2], int] = [
[0, 1, 2],
[10, 11, 12],
[20, 21, 22],
]
normal3m: Normal3Matrix[int] = [
[0, 1, 2],
[10, 11, 12],
[20, 21, 22],
]
enumm: NameEnumMatrix[string] = [
["Fn0", "Ln0", "Mn0"],
["Fn1", "Ln1", "Mn1"],
["Fn2", "Ln2", "Mn2"],
]
assert normalm.type.name == "NormalMatrix[range 0..2(int), range 0..2(int), system.int]"
assert normalm[2][2] == 22
assert normalm.Rows is range[0..2]
assert normalm.Cols is range[0..2]
assert normalm.T is int
assert normal3m.type.name == "Normal3Matrix[system.int]"
assert normal3m[2][2] == 22
assert normal3m.Rows is range[0..2]
assert normal3m.Cols is range[0..2]
assert normal3m.T is int
assert enumm.type.name == "NameEnumMatrix[system.string]"
assert enumm[2][NameEnum.middle_name] == "Mn2"
assert enumm[2][NameEnum(2)] == "Mn2"
assert enumm.Rows is range[0..2]
assert enumm.Cols is NameEnum
assert enumm.T is string
assert normalm * normal3m == [[0, 1, 4], [100, 121, 144], [400, 441, 484]]
type
StaticMatrix[Rows, Cols: static[int], T] = array[Rows, array[Cols, T]]
Static3Matrix[T] = StaticMatrix[3, 3, T]
var
staticm: StaticMatrix[3, 3, int] = [
[0, 1, 2],
[10, 11, 12],
[20, 21, 22],
]
static3m: Static3Matrix[int] = [
[0, 1, 2],
[10, 11, 12],
[20, 21, 22],
]
assert staticm.type.name == "StaticMatrix[3, 3, system.int]"
assert staticm.Rows == 3
assert staticm.Cols == 3
assert staticm.T is int
assert static3m.type.name == "Static3Matrix[system.int]"
assert static3m.Rows == 3
assert static3m.Cols == 3
assert static3m.T is int
assert staticm * static3m == [[0, 1, 4], [100, 121, 144], [400, 441, 484]]
type
#LwMatrix[Rows, Cols: static[range], T] = array[(high(Rows) + 1) * (high(Cols) + 1), T]
StaticLwMatrix[Rows, Cols: static[int], T] = array[Rows * Cols, T]
StaticLw3Matrix[T] = StaticLwMatrix[3, 3, T]
proc `[]`* (a: StaticLwMatrix, row, col: int): StaticLwMatrix.T =
if (row >= 0 and row >= StaticLwMatrix.Rows) or
(row < 0 and row < -StaticLwMatrix.Rows) or
(col >=0 and col >= StaticLwMatrix.Cols) or
(col < 0 and col < -StaticLwMatrix.Cols):
raise newException(IndexError, "index out of bounds")
a[StaticLwMatrix.Cols * row + (if col >= 0: col else: StaticLwMatrix.Cols + col)]
# same as above
proc `[]`* [Rows, Cols, T](a: StaticLwMatrix[Rows, Cols, T], row, col: T): T =
if (row >= 0 and row >= Rows) or
(row < 0 and row < -Rows) or
(col >=0 and col >= Cols) or
(col < 0 and col < -Cols):
raise newException(IndexError, "index out of bounds")
a[Cols * row + (if col >= 0: col else: Cols + col)]
var
staticlwm: StaticLwMatrix[3, 3, int] = [
0, 1, 2,
10, 11, 12,
20, 21, 22,
]
staticlw3m: StaticLw3Matrix[int] = [
0, 1, 2,
10, 11, 12,
20, 21, 22,
]
assert staticlwm.type.name == "StaticLwMatrix[3, 3, system.int]"
assert staticlwm.Rows == 3
assert staticlwm.Cols == 3
assert staticlwm.T is int
assert staticlw3m.type.name == "StaticLw3Matrix[system.int]"
assert staticlw3m.Rows == 3
assert staticlw3m.Cols == 3
assert staticlw3m.T is int
assert staticlw3m[1, -1] == 12
type
NormalMatrixObj[Rows, Cols, T] = object
data: array[Rows, array[Cols, T]]
NameEnumMatrixObj[Rows, T] = object
data: array[Rows, array[NameEnum, T]]
StaticMatrixObj[Rows, Cols: static[int], T] = object
data: array[Rows, array[Cols, T]]
var
normalm_obj = NormalMatrixObj[range[0..2], range[0..2], int](
data: [
[0, 1, 2],
[10, 11, 12],
[20, 21, 22],
]
)
enumm_obj = NameEnumMatrixObj[range[0..2], string](
data: [
["Fn0", "Ln0", "Mn0"],
["Fn1", "Ln1", "Mn1"],
["Fn2", "Ln2", "Mn2"],
]
)
staticm_obj = StaticMatrixObj[3, 3, int](
data: [
[0, 1, 2],
[10, 11, 12],
[20, 21, 22],
]
)
assert normalm_obj.type.name == "NormalMatrixObj[range 0..2(int), range 0..2(int), system.int]"
assert enumm_obj.type.name == "NameEnumMatrixObj[range 0..2(int), system.string]"
assert staticm_obj.type.name == "StaticMatrixObj[3, 3, system.int]"
Alternatively, the type operator can be used over the proc params for similar effect when anonymous or distinct type classes are used.
When a generic type is instantiated with a type class instead of a concrete type, this results in another more specific type class: