Emacs, scripting and anything text oriented.

Nim

Back to top
Collection of Nim snippets with brief notes as I try them out from the official Nim tutorial, my own musings, hard-learned lessons and other places.
Kaushal Modi
Table of Contents

Nim Help #

If on the devel branch and on a commit newer than 9cc8fec370, use nim --fullhelp1 — don’t use nim --help.

I ended up with above conclusion as I needed the --out: functionality but couldn’t find it in the default help (--help). I don’t see why anyone would ever use the less informative --help over --fullhelp.

If people are complaining about “too much information in help”, they should use it the right way.. pipe the help to grep or rg.

> nim --fullhelp | rg out
  -o:FILE, --out:FILE       set the output filename
  --stdout                  output to stdout
                            in the generated output

Echo #

Echo with newlines #

echo("Hello World")
Hello World

The parentheses are optional.

echo "Hello World"
Hello World

Another way is to use the writeLine proc to explicitly write to stdout File.

stdout.writeLine "Hello World!"
stdout.writeLine "Hello World again!!"
Hello World!
Hello World again!!

Echo without newlines #

stdout.write("Hello")
stdout.write("World")
HelloWorld

TODO Colored or Styled echo #

Use styledEcho macro from terminal module (which is syntactic sugar for stdout.styledWriteLine from that same module).

The syntax looks like this:

styledEcho <style1>, <style2>, <some string>, resetStyle, <some string>, <style3>, <some other string>, resetStyle

As shown above (<style1>, <style2>,), you can stack multiple “styles” together if they make sense.. like styleBright, fgBlue,.

Available Styles #

Style #
Style = enum
  styleBright = 1,            ## bright text
  styleDim,                   ## dim text
  styleUnknown,               ## unknown
  styleUnderscore = 4,        ## underscored text
  styleBlink,                 ## blinking/bold text
  styleReverse = 7,           ## unknown
  styleHidden                 ## hidden text
Foreground Color #
ForegroundColor = enum
  fgBlack = 30,               ## black
  fgRed,                      ## red
  fgGreen,                    ## green
  fgYellow,                   ## yellow
  fgBlue,                     ## blue
  fgMagenta,                  ## magenta
  fgCyan,                     ## cyan
  fgWhite                     ## white
Background Color #
BackgroundColor = enum
  bgBlack = 40,               ## black
  bgRed,                      ## red
  bgGreen,                    ## green
  bgYellow,                   ## yellow
  bgBlue,                     ## blue
  bgMagenta,                  ## magenta
  bgCyan,                     ## cyan
  bgWhite                     ## white
Other #
TerminalCmd = enum
  resetStyle,                 ## reset attributes
  fgColor,                    ## set foreground's true color
  bgColor                     ## set background's true color

Example #

import terminal
styledEcho "unstyled text", styleBright, fgGreen, "[PASS]", resetStyle, fgGreen, " Yay!"
styledEcho "unstyled text", styleBright, fgRed, "[FAIL]", resetStyle, fgRed, " Nay :("
unstyled text^[[1m^[[32m[PASS]^[[0m^[[32m Yay!
^[[0munstyled text^[[1m^[[31m[FAIL]^[[0m^[[31m Nay :(
^[[0m

The binary control char is manually replaced with ASCII “^[” in the above results block and more below. That’s so that they are visible in the Markdown/HTML, and the Org file also remains “ascii-diffable”.

Even if you don’t add resetStyle to end the styling, as you see above, styledEcho adds the ANSI code for resetting the style (^[[0m), but that’s after it adds a newline.

This works OK if a background color is not set earlier. But if the background color is set like in the below example, the resetStyle after newline is too late! The background color ends up leaking after the “ordinary text” in that example. It’s not visible here, but if you don’t trust me, see here 😄.

import terminal
stdout.styledWriteLine(fgWhite, bgRed, "white text in red background")
stdout.styledWriteLine("ordinary text")
^[[37m^[[41mwhite text in red background
^[[0mordinary text

The fix is to reset the style explicitly at the end of each styled echo as below:

import terminal
stdout.styledWriteLine(fgWhite, bgRed, "white text in red background", resetStyle)
stdout.styledWriteLine("ordinary text")
^[[37m^[[41mwhite text in red background^[[0m
^[[0mordinary text

Always end the styled echoes (styledEcho, styledWriteLine) with resetStyle.

A better fix would be if Nim PR #8047 get approved. Then the above manual appending of resetStyle won’t be needed.

Lower level styling procs/templates #

setForeGroundColor
Sets foreground color for the stdout output that follows. It is the same as calling stdout.setForeGroundColor.
setBackGroundColor
Sets background color for the stdout output that follows. It is the same as calling stdout.setBackGroundColor.
resetAttributes
Resets the styling/coloring. It is the same as calling stdout.resetAttributes.
import terminal
setForeGroundColor(fgGreen)
echo "green text"
echo "more green text"
setForeGroundColor(fgRed)
echo "red text"
resetAttributes()
echo "ordinary text"
^[[32mgreen text
more green text
^[[31mred text
^[[0mordinary text

Below is a similar code snippet, but stdout.write is used instead of echo to demonstrate changing of colors in the same line.

import terminal
setForeGroundColor(fgRed)
stdout.write("red text ")      # no newline here
setForeGroundColor(fgGreen)
stdout.write("green text ")    # no newline here
setForeGroundColor(fgBlue)
stdout.write("blue text ")     # no newline here
setForeGroundColor(fgYellow)
stdout.write("yellow text ")   # no newline here
resetAttributes()
stdout.write("ordinary text ") # no newline here
setForeGroundColor(fgCyan)
stdout.write("cyan text ")     # no newline here
resetAttributes()
echo ""
^[[31mred text ^[[32mgreen text ^[[34mblue text ^[[33myellow text ^[[0mordinary text ^[[36mcyan text ^[[0m

Standard Error #

To send message on stderr, use writeLine proc to explicitly write to stderr File.

stderr.writeLine "Error!"
quit 1

Above will generate this error:

Error!

To send messages to stderr that don’t end in newlines, use stderr.write instead.

stderr.write "Error1"
stderr.write "Error2"
quit 1

Above will generate this error:

Error1Error2

NEED TO UNDERSTAND When to use stdout.flushFile#

Lexical elements #

String Functions #

Refer to this separate set of notes where I compare the Nim String functions with those in Python 3.

String and character literals #

  • String literals are enclosed in double quotes
  • Character literals in single quotes.

Special characters are escaped with \: \n means newline, \t means tabulator, etc.

echo "Hello"
# echo 'Hello' # This will give error; single quotes are only for single character
echo 'a', 'b'
echo "c\n", '\t', 'b'
Hello
ab
c
	b

There are also raw string literals:

echo r"No need to escape \n and \t in here"
No need to escape \n and \t in here

In raw literals the backslash is not an escape character.

The third and last way to write string literals are long string literals like in Python. They are written with three quotes: """ ... """; they can span over multiple lines and the \ is not an escape character either.

echo """Hey look at this.
I can keep on typing over multiple lines,
with 'single' or "double" quotes and even
back slashes: \n \t \r"""
Hey look at this.
I can keep on typing over multiple lines,
with 'single' or "double" quotes and even
back slashes: \n \t \r

String concatenation #

Either & or , can be used to concatenate string in echo.

echo "abc" & "def"
echo "abc", "def"
abcdef
abcdef

Asterisks after identifiers like function names #

From https://nim-lang.org/docs/tut1.html#modules:

A module may gain access to the symbols of another module by using the import statement. Only top-level symbols that are marked with an asterisk (*) are exported.

I got curious about the * after I read code like below here:

proc getLinkableFiles*(appPath: string, dest: string=expandTilde("~")): seq[LinkInfo] =

strformat and fmt #

import strformat
let a = 100
echo fmt"a = {a}"
echo fmt"""a = {a}"""
a = 100
a = 100

Use string variable containing message formatting for fmt #

Below does not work:

import strformat
let
  a = 100
  msg = "a = {a}"

echo fmt(msg)

Gives error:

stack trace: (most recent call last)
lib/pure/strformat.nim(281) &
nim_src_ylMbyJ.nim(9, 10) Error: string formatting (fmt(), &) only works with string literals

Splitting Org header args #

import strutils
let args = """#+begin_src nim :tangle "foo.nim" :shebang "#!/usr/bin/env bash""""
echo args.split(" ") # parses arg vals with spaces incorrectly
echo args.split(":") # better
echo args.split(":")[0].strip.split(" ")
@["#+begin_src", "nim", ":tangle", "\"foo.nim\"", ":shebang", "\"#!/usr/bin/env", "bash\""]
@["#+begin_src nim ", "tangle \"foo.nim\" ", "shebang \"#!/usr/bin/env bash\""]
@["#+begin_src", "nim"]

Syntax #

Line continuation #

Nim does not have any line-continuation character – a character you can use to break a long line of code.

Instead line continuation is inferred if a line ends in an operator, ,, or any of these opening brackets ((, [, {).

See the below code. It works, but it has a really long line of code.

proc isTriangle(s: openArray[int]): bool =
  return (s.len == 3) and (s[0] <= s[1] + s[2]) and (s[1] <= s[2] + s[0]) and (s[2] <= s[0] + s[1]) and (s[0] > 0) and (s[1] > 0) and (s[2] > 0)
echo isTriangle([3, 3, 3])
true

Below will not work as the continued lines are not ending with an operator – You will get Error: invalid indentation.

I really wish this worked!

proc isTriangle(s: openArray[int]): bool =
  return (s.len == 3)
    and (s[0] <= s[1] + s[2]) and (s[1] <= s[2] + s[0]) and (s[2] <= s[0] + s[1])
    and (s[0] > 0) and (s[1] > 0) and (s[2] > 0)
echo isTriangle([3, 3, 3])

Below, I am ending the continued lines with the and operator. But that will fail with the same error too.. because I am not indenting the continued lines.

proc isTriangle(s: openArray[int]): bool =
  return (s.len == 3) and
  (s[0] <= s[1] + s[2]) and (s[1] <= s[2] + s[0]) and (s[2] <= s[0] + s[1]) and
  (s[0] > 0) and (s[1] > 0) and (s[2] > 0)
echo isTriangle([3, 3, 3])

Finally, below works because:

  • The continued lines are ending with an operator (and in this example).
  • When continued, they resume after an indentation.
proc isTriangle(s: openArray[int]): bool =
  return (s.len == 3) and
    (s[0] <= s[1] + s[2]) and (s[1] <= s[2] + s[0]) and (s[2] <= s[0] + s[1]) and
    (s[0] > 0) and (s[1] > 0) and (s[2] > 0)
echo isTriangle([3, 3, 3])
true

Ref

Templates and Macros #

TODO Templates #

NEED TO UNDERSTAND Macros #

  • Comment by @Vindaar from GitHub on fixing the macro here.
  • His further response on why result[^1].add(arg) was used instead of result.add(arg).

NEED TO UNDERSTAND Term Rewriting Macros #

Nim Manual – Term Rewriting Macros

Representing one type in another #

Table 1: Representing one type in another
From Type / To Typeboolcharintfloatstring
bool-N/Aintfloat$ or strformat.fmt or use plain logic to return string
charN/A-intfloat$ or strformat.fmt or custom logic using newString
intfloat.bool (or bool, int needs to be 0 or 1)float.char (or chr or char, int needs to be in [0,255])-float$ or strformat.fmt or strutils.intToStr
floatboolchar (truncation + rollover)int (truncation + rollover)-$ or strformat.fmt
stringstrutils.parseBoolas a seq of charstrutils.parseIntstrutils.parseFloat-

From bool #

bool to char #

not applicable

DONE bool to int #

import strformat, typetraits
let
  b_seq = @[true, false]
for b in b_seq:
  var b_int = b.int
  echo fmt"Value of {b.type.name} b is {b}"
  echo fmt"Value of {b_int.type.name} b_int is {b_int}"
Value of bool b is true
Value of int b_int is 1
Value of bool b is false
Value of int b_int is 0

DONE bool to float #

import strformat, typetraits
let b_seq = @[true, false]
for b in b_seq:
  var b_float = b.float
  echo fmt"Value of {b.type.name} b is {b}"
  echo fmt"Value of {b_float.type.name} b_float is {b_float}"
Value of bool b is true
Value of float b_float is 1.0
Value of bool b is false
Value of float b_float is 0.0

DONE bool to string #

import strformat, typetraits
let b_seq = @[true, false]
for b in b_seq:
  var b_string1 = $b
  var b_string2 = fmt"{b}"
  var b_string3 = if b: "true" else: "false"
  echo fmt"Value of {b.type.name} b is {b}"
  echo fmt"  Value of {b_string1.type.name} b_string1 is {b_string1}"
  echo fmt"  Value of {b_string2.type.name} b_string2 is {b_string2}"
  echo fmt"  Value of {b_string3.type.name} b_string3 is {b_string3}"
Value of bool b is true
  Value of string b_string1 is true
  Value of string b_string2 is true
  Value of string b_string3 is true
Value of bool b is false
  Value of string b_string1 is false
  Value of string b_string2 is false
  Value of string b_string3 is false

From char #

char to bool #

not applicable

DONE char to int #

import strformat, typetraits
let
  c = 'A'
  c_int = c.int
echo fmt"Value of {c.type.name} c is {repr(c)}"
echo fmt"Value of {c_int.type.name} c_int is {c_int}"
Value of char c is 'A'
Value of int c_int is 65

DONE char to float #

import strformat, typetraits
let
  c = 'A'
  c_float = c.float
echo fmt"Value of {c.type.name} c is {repr(c)}"
echo fmt"Value of {c_float.type.name} c_float is {c_float}"
Value of char c is 'A'
Value of float c_float is 65.0

DONE char to string #

import strformat, typetraits
from strutils import join
let c_seq = @['A', 'b', '@']
for c in c_seq:
  let
    c_string1 = $c
    c_string2 = fmt"{c}"
    c_string3 = @[c].join("")
  var c_string4 = newString(1)
  c_string4[0] = c
  echo fmt"Value of {c.type.name} c is {repr(c)}"
  echo fmt"  Value of {c_string1.type.name} c_string1 is {c_string1}"
  echo fmt"  Value of {c_string2.type.name} c_string2 is {c_string2}"
  echo fmt"  Value of {c_string3.type.name} c_string3 is {c_string3}"
  echo fmt"  Value of {c_string4.type.name} c_string4 is {c_string4}"
Value of char c is 'A'
  Value of string c_string1 is A
  Value of string c_string2 is A
  Value of string c_string3 is A
  Value of string c_string4 is A
Value of char c is 'b'
  Value of string c_string1 is b
  Value of string c_string2 is b
  Value of string c_string3 is b
  Value of string c_string4 is b
Value of char c is '@'
  Value of string c_string1 is @
  Value of string c_string2 is @
  Value of string c_string3 is @
  Value of string c_string4 is @
Join a sequence of characters into a string #
import strformat, typetraits
from strutils import join
let
  c_seq = @['a', 'b', 'c', 'd']
  str = c_seq.join("")
echo fmt"{str} is the stringified form of {c_seq.type.name} {c_seq}"
abcd is the stringified form of seq[char] @['a', 'b', 'c', 'd']

From int #

DONE int to bool #

  • The int value needs to be either 0 or 1.
  • RangeError exception is thrown for any other int value.
import strformat, typetraits
let
  i_seq = @[-1, 0, 1, 2]
for i in i_seq:
  echo fmt"Value of {i.type.name} i is {i}"
  var i_bool: bool
  try:
    i_bool = i.bool
    echo fmt"  Value of {i_bool.type.name} i_bool is {i_bool}"
  except:
    echo fmt"  [Error] {getCurrentException().name}: {getCurrentException().msg}"
Value of int i is -1
  [Error] RangeError: value out of range: -1
Value of int i is 0
  Value of bool i_bool is false
Value of int i is 1
  Value of bool i_bool is true
Value of int i is 2
  [Error] RangeError: value out of range: 2

One way to get around this limitation on int values is to first convert it to a float and then to a bool. With this, any non-zero value of the int will return true, and only the value of 0 will return false.

import strformat, typetraits
let i_seq = @[-1000, 0, 1, 1000]
for i in i_seq:
  echo fmt"Value of {i.type.name} i is {i}"
  var i_bool: bool
  try:
    i_bool = i.float.bool
    echo fmt"  Value of {i_bool.type.name} i_bool is {i_bool}"
  except:
    echo fmt"  [Error] {getCurrentException().name}: {getCurrentException().msg}"
Value of int i is -1000
  Value of bool i_bool is true
Value of int i is 0
  Value of bool i_bool is false
Value of int i is 1
  Value of bool i_bool is true
Value of int i is 1000
  Value of bool i_bool is true

DONE int to char #

  • The int value needs to be in the [0, 255] range.
  • RangeError exception is thrown if that value is outside that range.
import strformat, typetraits
let i_seq: seq[int] = @[-1, 0, 62, 256]
for i in i_seq:
  var i_char1, i_char2: char
  echo fmt"Value of {i.type.name} i is {i}"
  try:
    i_char1 = chr(i) # or i.chr
    echo fmt"  Value of {i_char1.type.name} i_char1 is {repr(i_char1)}"
  except:
    echo fmt"  [Error] {getCurrentException().name}: {getCurrentException().msg}"
  try:
    i_char2 = i.char # or char(i)
    echo fmt"  Value of {i_char2.type.name} i_char2 is {repr(i_char2)}"
  except:
    echo fmt"  [Error] {getCurrentException().name}: {getCurrentException().msg}"
Value of int i is -1
  [Error] RangeError: value out of range: -1
  [Error] RangeError: value out of range: -1
Value of int i is 0
  Value of char i_char1 is '\0'
  Value of char i_char2 is '\0'
Value of int i is 62
  Value of char i_char1 is '>'
  Value of char i_char2 is '>'
Value of int i is 256
  [Error] RangeError: value out of range: 256
  [Error] RangeError: value out of range: 256

chr converts only an 8-bit unsigned integer (range[0 .. 255]).

One way to get around this limitation on int values is to first convert it to a float and then to a char. With this, any value of int < 0 or >= 256 will returned a rolled-over char value.

import strformat, typetraits
let i_seq = @[-2, -1, 0, 1, 254, 255, 256, 257]
for i in i_seq:
  var i_char = i.float.char
  echo fmt"Value of {i.type.name} i is {i}"
  echo fmt"  Value of {i_char.type.name} i_char is {repr(i_char)}"
Value of int i is -2
  Value of char i_char is '\254'
Value of int i is -1
  Value of char i_char is '\255'
Value of int i is 0
  Value of char i_char is '\0'
Value of int i is 1
  Value of char i_char is '\1'
Value of int i is 254
  Value of char i_char is '\254'
Value of int i is 255
  Value of char i_char is '\255'
Value of int i is 256
  Value of char i_char is '\0'
Value of int i is 257
  Value of char i_char is '\1'

DONE int to float #

import strformat, typetraits
let i_seq: seq[int] = @[-1, 0, 1000]
for i in i_seq:
  var i_float = i.float
  echo fmt"Value of {i.type.name} i is {i}"
  echo fmt"Value of {i_float.type.name} i_float is {i_float}"
Value of int i is -1
Value of float i_float is -1.0
Value of int i is 0
Value of float i_float is 0.0
Value of int i is 1000
Value of float i_float is 1000.0

DONE int to string #

import strformat, strutils, typetraits
let i_seq: seq[int] = @[-1000, 0, 1000]
for i in i_seq:
  echo fmt"Value of {i.type.name} i is {i}"
  var
    i_str1 = $i
    i_str2 = fmt"{i}"
    i_str3 = intToStr(i) # strutils
  echo fmt"  Value of {i_str1.type.name} i_str1 is {i_str1}"
  echo fmt"  Value of {i_str2.type.name} i_str2 is {i_str2}"
  echo fmt"  Value of {i_str3.type.name} i_str3 is {i_str3}"
Value of int i is -1000
  Value of string i_str1 is -1000
  Value of string i_str2 is -1000
  Value of string i_str3 is -1000
Value of int i is 0
  Value of string i_str1 is 0
  Value of string i_str2 is 0
  Value of string i_str3 is 0
Value of int i is 1000
  Value of string i_str1 is 1000
  Value of string i_str2 is 1000
  Value of string i_str3 is 1000
Why is even intToStr needed? #

As we see above $ provides a very concise way to print integers as strings. On the other hand, intToStr is verbose and an oddly named function.. the only one named as fooToBar (we don’t have strToInt, but parseInt.. I wish it were named as the former). Also, if one wants to use intToStr, they need to first import strutils.

From intToStr docs:

proc intToStr(x: int; minchars: Positive = 1): string {..}

Converts x to its decimal representation.

The resulting string will be minimally minchars characters long. This is achieved by adding leading zeros.

So intToStr would be used to add leading zeros:

import strutils
let i_arr = [-100, -50, 0, 0, 123, 1000]
for i in i_arr:
  echo intToStr(i, 10)
-0000000100
-0000000050
0000000000
0000000000
0000000123
0000001000

The minchars parameter in intToStr does not count the negative sign character.

But then, a similar result also can be achieved by the general formatting fmt function from strformat. See below to see what I mean by “similar”.

import strformat
let i_arr = [-100, -50, 0, 0, 123, 1000]
for i in i_arr:
  echo fmt"{i: 011}"
-0000000100
-0000000050
 0000000000
 0000000000
 0000000123
 0000001000

Breakdown of the “ 011” format specifier —

Here is the general fmt format specifier syntax:

[[fill]align][sign][#][0][minimumwidth][.precision][type]
  • The initial space is the ‘[sign]’ part which indicates that space should be used for positive numbers. I used that so that the positive numbers align well (right align) with the negative numbers.
  • The “0” is the ‘[0]’ part, which 0-pads the numbers on the left.
  • “11” is the ‘[minimumwidth]’ part which counts the “-” sign character too. The minimum width is set to 11 to match the 10 argument given to the intToStr in the above example.

I prefer the fmt behavior better. But also, as floatToStr does not exist, and I would anyways need to resort to fmt for formatting negative floats, I might as well do the same for negative ints too — Consistency.

So, still.. there isn’t a strong case to use intToStr.

From float #

DONE float to bool #

  • Any float value between (-1.0, 1.0) is false.
  • All other float values are true.
import strformat, typetraits
let
  f_seq = @[-1.0, -0.99, -0.1, 0.0, 0.3, 0.5, 0.8, 1.0, 1.1, 2.0]
for f in f_seq:
  var f_bool = f.bool
  echo fmt"Value of {f.type.name} f is {f}"
  echo fmt"Value of {f_bool.type.name} f_bool is {f_bool}"
Value of float f is -1.0
Value of bool f_bool is true
Value of float f is -0.99
Value of bool f_bool is false
Value of float f is -0.1
Value of bool f_bool is false
Value of float f is 0.0
Value of bool f_bool is false
Value of float f is 0.3
Value of bool f_bool is false
Value of float f is 0.5
Value of bool f_bool is false
Value of float f is 0.8
Value of bool f_bool is false
Value of float f is 1.0
Value of bool f_bool is true
Value of float f is 1.1
Value of bool f_bool is true
Value of float f is 2.0
Value of bool f_bool is true

DONE float to char (truncation + rollover) #

Floats whose truncated integer values are >= 256 or < 0 will roll-over to a char value in the range '\0' .. '\255' as shown in the below snippet.

import strformat, typetraits
let f_seq: seq[float] = @[-2.0, -1.5, -1.1, 0.0, 0.4, 1.0, 65.0, 65.3, 255.0, 256.0, 257.0]
for f in f_seq:
  var f_char = f.char
  echo fmt"Value of {f.type.name} f is {f}"
  echo fmt"Value of {f_char.type.name} f_char is {repr(f_char)}"
Value of float f is -2.0
Value of char f_char is '\254'
Value of float f is -1.5
Value of char f_char is '\255'
Value of float f is -1.1
Value of char f_char is '\255'
Value of float f is 0.0
Value of char f_char is '\0'
Value of float f is 0.4
Value of char f_char is '\0'
Value of float f is 1.0
Value of char f_char is '\1'
Value of float f is 65.0
Value of char f_char is 'A'
Value of float f is 65.3
Value of char f_char is 'A'
Value of float f is 255.0
Value of char f_char is '\255'
Value of float f is 256.0
Value of char f_char is '\0'
Value of float f is 257.0
Value of char f_char is '\1'

DONE float to int (truncation + rollover) #

Note from the below example that rollover happens at extremes of the int values.

import strformat, typetraits, math
let f_seq: seq[float] = @[low(int).float, (low(int)/2).float, -1.9, -1.5, -1.1, 0.0, 0.4, 0.5, 0.9, 1.0, (high(int)/2).float, high(int).float]
for f in f_seq:
  var f_int = f.int
  echo fmt"Value of {f.type.name} f is {f}"
  echo fmt"Value of {f_int.type.name} f_int is {f_int}"
Value of float f is -9.223372036854776e+18
Value of int f_int is -9223372036854775808
Value of float f is -4.611686018427388e+18
Value of int f_int is -4611686018427387904
Value of float f is -1.9
Value of int f_int is -1
Value of float f is -1.5
Value of int f_int is -1
Value of float f is -1.1
Value of int f_int is -1
Value of float f is 0.0
Value of int f_int is 0
Value of float f is 0.4
Value of int f_int is 0
Value of float f is 0.5
Value of int f_int is 0
Value of float f is 0.9
Value of int f_int is 0
Value of float f is 1.0
Value of int f_int is 1
Value of float f is 4.611686018427388e+18
Value of int f_int is 4611686018427387904
Value of float f is 9.223372036854776e+18
Value of int f_int is -9223372036854775808

DONE float to string #

import strformat, typetraits
let f_seq: seq[float] = @[-1.9, -1.5, -1.1, 0.0, 0.4, 0.5, 0.9, 1.0]
for f in f_seq:
  var
    f_string1 = $f
    f_string2 = fmt"{f}"
  echo fmt"Value of {f.type.name} f is {f}"
  echo fmt"  Value of {f_string1.type.name} f_string1 is {f_string1}"
  echo fmt"  Value of {f_string2.type.name} f_string2 is {f_string2}"
Value of float f is -1.9
  Value of string f_string1 is -1.9
  Value of string f_string2 is -1.9
Value of float f is -1.5
  Value of string f_string1 is -1.5
  Value of string f_string2 is -1.5
Value of float f is -1.1
  Value of string f_string1 is -1.1
  Value of string f_string2 is -1.1
Value of float f is 0.0
  Value of string f_string1 is 0.0
  Value of string f_string2 is 0.0
Value of float f is 0.4
  Value of string f_string1 is 0.4
  Value of string f_string2 is 0.4
Value of float f is 0.5
  Value of string f_string1 is 0.5
  Value of string f_string2 is 0.5
Value of float f is 0.9
  Value of string f_string1 is 0.9
  Value of string f_string2 is 0.9
Value of float f is 1.0
  Value of string f_string1 is 1.0
  Value of string f_string2 is 1.0

From string #

DONE string to bool #

import strformat, strutils, typetraits
let s_seq = @["true", "True", "tRuE", "false", "False", "FaLsE"]
for s in s_seq:
  var s_bool = parseBool(s)
  echo fmt"Value of {s.type.name} s is {s}"
  echo fmt"Value of {s_bool.type.name} s_bool is {s_bool}"
Value of string s is true
Value of bool s_bool is true
Value of string s is True
Value of bool s_bool is true
Value of string s is tRuE
Value of bool s_bool is true
Value of string s is false
Value of bool s_bool is false
Value of string s is False
Value of bool s_bool is false
Value of string s is FaLsE
Value of bool s_bool is false

DONE string to char #

A string can be represented as a sequence of chars as shown below.

import strformat, typetraits
let s = "abcd"
var c_seq: seq[char]
for c in s:
  c_seq.add(c)
echo fmt"Value of {s.type.name} s is {s}"
echo fmt"Value of {c_seq.type.name} c_seq is {c_seq}"
Value of string s is abcd
Value of seq[char] c_seq is @['a', 'b', 'c', 'd']

DONE string to int #

import strformat, strutils, typetraits
let
  s = "1212"
  s_int = parseInt(s)
echo fmt"Value of {s.type.name} s is {s}"
echo fmt"Value of {s_int.type.name} s_int is {s_int}"
Value of string s is 1212
Value of int s_int is 1212

DONE string to float #

import strformat, strutils, typetraits
let
  s = "12.12"
  s_float = parseFloat(s)
echo fmt"Value of {s.type.name} s is {s}"
echo fmt"Value of {s_float.type.name} s_float is {s_float}"
Value of string s is 12.12
Value of float s_float is 12.12

Data Types #

TODO int vs int32 vs int64 #

Char and repr #

When printing char values for debug, it’s better to print using repr so that unprintable chars like ACK (\6) and BELL (\7) can be easily distinguished. See below for example:

for c in @['\6', '\7', '\32', '\33', '\65', 'B']:
  echo "char with ascii value of ", c.int, " = ", repr(c)
char with ascii value of 6 = '\6'
char with ascii value of 7 = '\7'
char with ascii value of 32 = ' '
char with ascii value of 33 = '!'
char with ascii value of 65 = 'A'
char with ascii value of 66 = 'B'

Getting min and max for various data types #

Using low and high #

import math, strformat
let
  int_low = low(int)
  int_low_2power = log2(-int_low.float).int
  int32_low = low(int32)
  int32_low_2power = log2(-int32_low.float).int
  int64_low = low(int64)
  int64_low_2power = log2(-int64_low.float).int

echo fmt"int:     {low(int)} (-2^{int_low_2power}) -> {high(int)} (2^{int_low_2power} - 1)"
echo fmt"int32:   {low(int32)} (-2^{int32_low_2power}) -> {high(int32)} (2^{int32_low_2power} - 1)"
echo fmt"int64:   {low(int64)} (-2^{int64_low_2power}) -> {high(int64)} (2^{int64_low_2power} - 1)"
echo fmt"float:   {low(float)} -> {high(float)}"
echo fmt"float32: {low(float32)}  -> {high(float32)}"
echo fmt"float64: {low(float64)}  -> {high(float64)}"
int:     -9223372036854775808 (-2^63) -> 9223372036854775807 (2^63 - 1)
int32:   -2147483648 (-2^31) -> 2147483647 (2^31 - 1)
int64:   -9223372036854775808 (-2^63) -> 9223372036854775807 (2^63 - 1)
float:   -inf -> inf
float32: -inf  -> inf
float64: -inf  -> inf

Using sizeof #

From sizeof docs:

returns the size of x in bytes. Since this is a low-level proc, its usage is discouraged - using new for the most cases suffices that one never needs to know x’s size.

As a special semantic rule, x may also be a type identifier (sizeof(int) is valid).

import strformat
echo fmt"Size of bool is {sizeof(bool)} byte"
echo fmt"Size of char is {sizeof(char)} byte"
echo fmt"Size of int is {sizeof(int)} bytes"
echo fmt"Size of int32 is {sizeof(int32)} bytes"
echo fmt"Size of int64 is {sizeof(int64)} bytes"
echo fmt"Size of float is {sizeof(float)} bytes"
echo fmt"Size of float32 is {sizeof(float32)} bytes"
echo fmt"Size of float64 is {sizeof(float64)} bytes"
Size of bool is 1 byte
Size of char is 1 byte
Size of int is 8 bytes
Size of int32 is 4 bytes
Size of int64 is 8 bytes
Size of float is 8 bytes
Size of float32 is 4 bytes
Size of float64 is 8 bytes

Boolean checking “unset” variables #

isNil on string #

import strformat, typetraits
var s: string
echo fmt"initial value of {s.type.name} s = `{s}', isNil? {s.isNil}"
initial value of string s = `', isNil? true

isNil non-string primitive types #

isNil does not work for non-string primitive types like bool, char, int and float.

import strformat, typetraits
var
  b: bool
  c: char
  i: int
  f: float
echo fmt"initial value of {b.type.name} b = {b}, isNil? {b.isNil}"
echo fmt"initial value of {c.type.name} c = {c}, isNil? {c.isNil}"
echo fmt"initial value of {i.type.name} i = {i}, isNil? {i.isNil}"
echo fmt"initial value of {f.type.name} f = {f}, isNil? {f.isNil}"

Trying to evaluate the above gives this error (and then the similar for those other types in that code snippet too):

lib/pure/strformat.nim(313, 39) Error: type mismatch: got <bool>
but expected one of:
proc isNil[T](x: seq[T]): bool
proc isNil(x: cstring): bool
proc isNil(x: string): bool
proc isNil[T](x: ptr T): bool
proc isNil[T](x: ref T): bool
proc isNil(x: pointer): bool
proc isNil[T: proc](x: T): bool

expression: isNil(b)

isNil on File object #

Thanks to mashingan from Nim Forum for this tip.

var fo: File
echo "Before open: Is fo 'unset'? ", fo.isNil
fo = open("./nim.org")
echo "After open: Is fo 'unset'? ", fo.isNil
fo.close
echo "After close: Is fo 'unset'? ", fo.isNil
fo = nil
echo "After explicitly setting to nil: Is fo 'unset'? ", fo.isNil
Before open: Is fo 'unset'? true
After open: Is fo 'unset'? false
After close: Is fo 'unset'? false
After explicitly setting to nil: Is fo 'unset'? true

Rune #

  • The functions from strutils like isAlphaNumeric, isAlphaAscii work on chars and strings.
  • The functions from unicode like isAlpha, isLower, isUpper, etc. work on Runes and strings.
from strutils import isAlphaNumeric, isAlphaAscii
import unicode
echo 'a'
echo 'a'.isAlphaAscii()
echo 'a'.isAlphaNumeric()
# echo 'a'.isAlpha() # this gives error: nim_src_YQy3FE.nim(6, 9) Error: type mismatch: got <char>
echo 'a'.Rune
echo 'a'.Rune.isLower()
echo 'a'.Rune.isUpper()
echo 'a'.Rune.isAlpha()
a
true
true
a
true
false
true

Math #

Log #

log #

Credit for the below log function goes to @Paalon from GitHub [ref]:

import math
proc log [X, B: SomeFloat](x: X, base: B = E): auto =
  ## Computes the logarithm ``base`` of ``x``.
  ## If ``base`` is not specified, it defaults to the natural base ``E``.
  when B is float64 or X is float64:
    var r: float64
  else:
    var r: float32
  if base == E:
    r = ln(x)
  else:
    r = ln(x) / ln(base)
  return r
import random, strformat
echo fmt"log10(111) = {log(111.float, 10.float)}"
echo fmt"log2(8) = {log(8.float, 2.float)}"
echo fmt"ln(8) = {log(8.float, E)}"
echo fmt"ln(8) = {log(8.float)}"
log10(111) = 2.045322978786657
log2(8) = 3.0
ln(8) = 2.079441541679836
ln(8) = 2.079441541679836

log2 #

import math, strformat
var
  val = 8
  power2 = log2(val.float) # need to cast val to a float
echo fmt"2^{power2} = {val}"
2^3.0 = 8

log10 #

import math, strformat
var
  val = 100
  power10 = log10(val.float) # need to cast val to a float
echo fmt"10^{power10} = {val}"
10^2.0 = 100

Exponentiation #

Using ^ from math module (Non-negative integer exponentiation) #

Need to import math module for the exponentiation operator ^ to work.

You can do X ^ Y where X can be an integer or float, but Y has to be an integer >= 0.

import math
echo (2^0)
echo (2^3)
echo (2.2^3)
1
8
10.648

Using pow from math module (Float exponentiation) #

As mentioned above, X ^ Y works only if Y is an integer and >= 0.

But what if Y is negative, or not an integer? .. In that case, you need to use the pow function from the same math module.

import math
echo "Evaluating equivalent of 2^-1:"
echo pow(2.0, -1.0)
Evaluating equivalent of 2^-1:
0.5
About pow and floats #

Note that using pow has a requirement – Both of its arguments need to be floats. So the below snippet will fail:

import math
echo pow(2, 1)
nim_src_We7gQw.nim(5, 9) Error: ambiguous call; both math.pow(x: float64, y: float64)[declared in lib/pure/math.nim(265, 7)] and math.pow(x: float32, y: float32)[declared in lib/pure/math.nim(264, 7)] match for: (int literal(2), int literal(1))

But below will work:

import math
echo pow(2.0, 1.0)
echo pow(2.float, 1.float) # same as above
2.0
2.0

Random #

Random bool #

import random
proc randBool(): bool =
  result = rand(1).bool
for _ in 0 .. 5:
  echo randBool()
true
false
true
true
false
false

Random range #

Use rand(LOWER .. UPPER).

import random
for _ in 0 .. 5:
  echo rand(4 .. 5)
5
4
5
5
4
4

Works with negatives too:

import random
for _ in 0 .. 5:
  echo rand(-5 .. 5)
-4
5
0
0
2
-2

Call randomize before rand #

If you call just the rand function without first calling randomize, you will get the very same random value each time.

import random
echo rand(high(int))
Code Snippet 1: rand without randomize
4292486321577947087

In order to get truly random output, first call randomize and then rand. If you evaluate the below snippet, you will very likely get a different output each time, unlike the above snippet.

import random
randomize()
echo rand(high(int))
Code Snippet 2: rand with randomize, no seed
1682709059413540781
Specifying randomization seed #

You can also have a use-case where you want to specify a particular randomization seed. In that case, pass that integer seed to the randomize function.

Re-evaluating below will result in the same output each time, like in 1, but with the difference that you can control the randomization seed.

import random
randomize(123)
echo rand(high(int))
Code Snippet 3: rand with randomize, with seed
8452497653883
randomize(0) disables randomization #

From tests, it looks like randomize(0) disables randomization altogether and hard-codes the “randomized” var to 0!

Whether this is intended or not, this behavior is quite odd.

import random
echo "Setting randomization seed to a non-zero value; value of 1 is picked arbitrarily:"
randomize(1)
for _ in 0 .. 4:
  echo rand(high(int))
echo "\nNow setting randomization seed to 0:"
randomize(0)
for _ in 0 .. 4:
  echo rand(high(int))
Setting randomization seed to a non-zero value; value of 1 is picked arbitrarily:
68719493121
38280734540038433
1153018330890649890
1842080154353508865
97957842178638929

Now setting randomization seed to 0:
0
0
0
0
0

rand range #

import random
let k = 3
randomize(2) # arbitrarily picked seed
for _ in 0 .. (2*k):
  echo rand(k)
2
2
0
3
2
0
1

As seen above, rand(k) returns a random value in the range [0,k] i.e. including the “k” value.

Limit of rand #

rand accepts int (I think that is same is int64 – because this) and float (probably same as float32? or float64?.. couldn’t tell from that).

From random.rand docs:

Returns a random number in the range 0 .. max.

As rand accepts int, and it’s size is same as int64, and is signed (use uint for unsigned), the max positive number that rand can generate is 2^63 - 1.

NEED TO UNDERSTAND Need to cast rand input to int. Why? #
import random, strformat
echo fmt"rand(9223372036854775807.int) = {rand(9223372036854775807.int)}"
rand(9223372036854775807.int) = 4292486321577947087

Not doing so in the below code snippet:

import random, strformat
echo fmt"rand(9223372036854775807) = {rand(9223372036854775807)}"

gives Error: type mismatch: got <int64>:

nim_src_yz0syY.nim(5, 10) Error: type mismatch: got <int64>
but expected one of:
proc rand[T](r: var Rand; x: HSlice[T, T]): T
proc rand(max: float): float
proc rand(max: int): int
proc rand(r: var Rand; max: float): float
proc rand[T](r: var Rand; a: openArray[T]): T
proc rand[T](a: openArray[T]): T
proc rand[T](x: HSlice[T, T]): T
proc rand(r: var Rand; max: int): int

expression: rand(9223372036854775807'i64)

Quotient and Remainder #

If you do “7 / 2”, the quotient is 3, and the remainder is 1.

Quotient #

The quotient is calculated using the binary operator div which basically does integer division.

  • The result is negative if either of the dividend or the divisor is negative; else it is positive.
import strformat
echo "divisor = 3"
for i in -4 .. 4:
  echo fmt"  {i:2} div  3 = {i div 3:2}"
echo "\ndivisor = -3"
for i in -4 .. 4:
  echo fmt"  {i:2} div -3 = {i div -3:2}"
divisor = 3
  -4 div  3 = -1
  -3 div  3 = -1
  -2 div  3 =  0
  -1 div  3 =  0
   0 div  3 =  0
   1 div  3 =  0
   2 div  3 =  0
   3 div  3 =  1
   4 div  3 =  1

divisor = -3
  -4 div -3 =  1
  -3 div -3 =  1
  -2 div -3 =  0
  -1 div -3 =  0
   0 div -3 =  0
   1 div -3 =  0
   2 div -3 =  0
   3 div -3 = -1
   4 div -3 = -1

Remainder / Modulo operator #

The remainder is calculated using the binary operator, Modulo operator, mod.

  • The result has the sign of the dividend i.e. the sign of the divisor is ignored.
import strformat
echo "divisor = 3"
for i in -4 .. 4:
  echo fmt"  {i:2} mod  3 = {i mod 3:2}"
echo "\ndivisor = -3"
for i in -4 .. 4:
  echo fmt"  {i:2} mod -3 = {i mod -3:2}"
divisor = 3
  -4 mod  3 = -1
  -3 mod  3 =  0
  -2 mod  3 = -2
  -1 mod  3 = -1
   0 mod  3 =  0
   1 mod  3 =  1
   2 mod  3 =  2
   3 mod  3 =  0
   4 mod  3 =  1

divisor = -3
  -4 mod -3 = -1
  -3 mod -3 =  0
  -2 mod -3 = -2
  -1 mod -3 = -1
   0 mod -3 =  0
   1 mod -3 =  1
   2 mod -3 =  2
   3 mod -3 =  0
   4 mod -3 =  1

Incrementing/Decrementing #

Incrementing #

inc proc increments variables of Ordinal types: int, char, enum.

inc a is similar to doing a = a + 1 or a += 1 for int or char type variable a.

var a = 100
echo a
inc a
echo a
inc a
echo a
100
101
102
var a = 'b'
echo a
inc a
echo a
inc a
echo a
b
c
d
type
  Direction = enum
    north, east, south, west
var a = north
echo a
inc a
echo a
inc a
echo a
north
east
south

Decrementing #

dec proc decrements variables of Ordinal types: int, char, enum.

dec a is similar to doing a = a - 1 or a -= 1 for int or char type variable a.

var a = 100
echo a
dec a
echo a
dec a
echo a
100
99
98
var a = 'b'
echo a
dec a
echo a
dec a
echo a
b
a
`
type
  Direction = enum
    north, east, south, west
var a = west
echo a
dec a
echo a
dec a
echo a
west
south
east

Variable “Types” #

Table 2: var vs let vs const
KeywordVariable typeMust be initialized?Can be set during runtime?Should be evallable at compile?
varMutableNo (type must be specified though if not initialized)Yes, multiple timesNo
letImmutableYes, though these can be initialized at run time.Yes, just onceNo
constConstantYesNoYes

Mutable variables (var#

var variables are mutable.

var
  a = "foo"
  b = 0
  c: int   # Works fine, initialized to 0

# Works fine, `a` is mutable
a.add("bar")
echo a

b += 1
echo b

c = 3
echo c
c = 7
echo c
foobar
1
3
7

Immutable variables (let#

let variables are not mutable, but they can be set at run time.

let
  d = "foo"
  e = 5
  # Compile-time error, must be initialized at creation
  # f: float                      # Below line fixes the error
  f: float = 2.2

# Compile-time error, `d` and `e` are immutable
# Below 2 lines are commented out to fix the compilation error
# d.add("bar")
# e += 1

echo d
echo e
echo f
foo
5
2.2

Constants (const#

const “variables” are not mutable, and they have to be set at compile time.

# Computed at compilation time
const
  s = "abcdef"
  sLen = s.len
echo s
echo sLen
abcdef
6

let vs const #

The difference between let and const is:

  • let allows a variable value to be assigned at run time (though it cannot be re-assigned).

    let input = readLine(stdin)   # works
  • const means “enforce compile time evaluation and put it into a data section” i.e. you should be able to evaluate the value of a const variable during compile time. So setting a const variable using readLine (that takes user input at run time) will result in an error.

    const input = readLine(stdin) # Error: constant expression expected

return keyword and result Variable #

Below example shows the use of return keyword:

proc getAlphabet(): string =
  var accm = ""
  for letter in 'a' .. 'z':  # see iterators
    accm.add(letter)
  return accm
echo getAlphabet()
Code Snippet 4: getAlphabet function implementation without using result variable
abcdefghijklmnopqrstuvwxyz

The result variable is a special variable that serves as an implicit return variable, which exists because the control flow semantics of the return statement are rarely needed. The result variable is initialized in the standard way, as if it was declared with:

var result: ReturnType

For example, the getAlphabet() function above could be rewritten more concisely as:

proc getAlphabet(): string =
  result = ""
  for letter in 'a' .. 'z':
    result.add(letter)
echo getAlphabet()
Code Snippet 5: getAlphabet function implementation using result variable
abcdefghijklmnopqrstuvwxyz

A possible gotcha is declaring a new variable called result and expecting it to have the same semantics.

proc unexpected(): int =
  var result = 5 # Uh-oh, here 'result' got declared as a local variable because of 'var' keyword
  result += 5

echo unexpected()  # Prints 0, not 10
0

Checking if a variable is declared #

The compile-time procedure declared returns true if a variable is simply declared.. not necessarily assigned a value.

var
  foo: bool
  bar: bool = true

echo "Is the uninitialized variable 'foo' declared? ", declared(foo)
echo "Is the initialized variable 'bar' declared? ", declared(bar)
echo "Is the undeclared variable 'zoo' declared (duh)? ", declared(zoo)
Is the uninitialized variable 'foo' declared? true
Is the initialized variable 'bar' declared? true
Is the undeclared variable 'zoo' declared (duh)? false

Checking if a variable is defined #

Special compile-time proc to check if a certain -d:foo or --define:foo switch was used during compilation.

From Nim Docs – defined:

Special compile-time procedure that checks whether x is defined. x is an external symbol introduced through the compiler’s -d:x switch to enable build time conditionals:

when not defined(release):
  # Do here programmer friendly expensive sanity checks.
# Put here the normal code

For example, below code was compiled and run using nim c -r -d:foo --define:zoo test_defined.nim:

echo "Is '-d:foo' or '--define:foo' flag passed to the compiler? ", defined(foo)
echo "Is '-d:bar' or '--define:bar' flag passed to the compiler? ", defined(bar)
echo "Is '-d:zoo' or '--define:zoo' flag passed to the compiler? ", defined(zoo)
Is '-d:foo' or '--define:foo' flag passed to the compiler? true
Is '-d:bar' or '--define:bar' flag passed to the compiler? false
Is '-d:zoo' or '--define:zoo' flag passed to the compiler? true

Thanks to @Yardanico from GitHub and @mratsim from GitHub to help understand this proc’s use.

Variable Auto-initialization #

Global variables (like the ones in below example) cannot remain uninitialized in Nim.. they are always auto-initialized to some value.

var
  fooBool: bool
  fooInt: int
  fooFloat: float
  fooString: string
  fooSeqString: seq[string]

echo "Value of 'uninitialized' variable 'fooBool': ", fooBool
echo "Value of 'uninitialized' variable 'fooInt': ", fooInt
echo "Value of 'uninitialized' variable 'fooFloat': ", fooFloat
echo "Value of 'uninitialized' variable 'fooString': ", fooString
echo "Value of 'uninitialized' variable 'fooSeqString': ", fooSeqString
Code Snippet 6: Global variables always get auto-initialized
Value of 'uninitialized' variable 'fooBool': false
Value of 'uninitialized' variable 'fooInt': 0
Value of 'uninitialized' variable 'fooFloat': 0.0
Value of 'uninitialized' variable 'fooString':
Value of 'uninitialized' variable 'fooSeqString': nil

Uninitialized variables #

From section Variable Auto-initialization, we see that global variables always get auto-initialized.

Thanks to @data-man from GitHub, I learn that local variables can remain uninitialized, with the use of the noinit2 pragma.

Local variables would be the ones like the var variables inside a proc.

As global variables always auto-initialize, the {.noinit.} pragma will not work in the below snippet. The ui_i var still auto-initializes with all elements set to 0.

Nim should have thrown an error if someone tried to use {.noinit.} for global vars, but instead it silently does nothing!

var ui_i {.noinit.}: array[3, int]
echo "Value of uninitialized variable 'ui_i': ", ui_i
Code Snippet 7: noinit pragma does not work for global variables
Value of uninitialized variable 'ui_i': [0, 0, 0]

Pollute Stack #

A little function PolluteStack to help test if {.noinit.} works.

import random, math
proc polluteStack() =
  var
    c: char = (rand(high(char).int)).char
    i32: int32 = (rand(high(int32).int)).int32
    i64: int64 = (rand(high(int64).int)).int64
    f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rand(high(int64).int)).float64
    b: bool = rand(1).bool

I came up with the above function after reading this priceless tip by Stefan Salevski:

You may try to call another proc which writes some values to local variables, that should pollute the stack. And then call your ff proc.

I am open to learn better ways to pollute the stack.

Break-down of the polluteStack function #

We will use just this one line for analysis, as the rest are similar.

f32: float32 = (rand(high(int64).int)).float32
  • f32 is of type float32. So whatever we are assigning it to must be of that type. Here, that whatever is (rand(high(int64).int)), and we are casting it to float32 type using (WHATEVER).float32.
  • So here whatever is rand which takes in high(int64).int as input.
  • The input to rand is casted to int using .int because as of writing this, it did not accept inputs of type int64.
    • high(int64) returns the maximum value of int64 type.
  • Both float32 and float64 have min value of -inf and max value of +inf. The 32-bit/64-bit only changes the number of bytes using internally for storing those float values. So it is OK to use the same rand(high(int64).int to generate a random number for both of these float types as long as we are casting them using the right type for the right variable.

For simplicity, this function generates only positive random values for each type.

Test polluteStack #
import random, math
proc polluteStack() =
  var
    c: char = (rand(high(char).int)).char
    i32: int32 = (rand(high(int32).int)).int32
    i64: int64 = (rand(high(int64).int)).int64
    f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rand(high(int64).int)).float64
    b: bool = rand(1).bool
  echo repr(c)
  echo i32
  echo i64
  echo f32
  echo f64
  echo b
randomize(123) # arbitarily picked seed
polluteStack()
'{'
805341723
3469772516323406999
4.753479106664858e+17
7.875093343280577e+18
true

{.noinit.} does not work in block too #

I thought that variables in a block would be local for the purpose of {.noinit.}. But they are not i.e. the no-initialization does not work here too!

import random, math
proc polluteStack() =
  var
    c: char = (rand(high(char).int)).char
    i32: int32 = (rand(high(int32).int)).int32
    i64: int64 = (rand(high(int64).int)).int64
    f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rand(high(int64).int)).float64
    b: bool = rand(1).bool

# local var in block
block foo:
  randomize(123) # arbitarily picked seed
  polluteStack()
  var ui_i {.noinit.}: array[3, int]
  echo "In block: Value of uninitialized variable 'ui_i' in block: ", ui_i
In block: Value of uninitialized variable 'ui_i' in block: [0, 0, 0]

Works for var variables in proc #

Finally, the below code returns random values as that local var, local to a proc will actually remain uninitialized:

import random, math
proc polluteStack() =
  var
    c: char = (rand(high(char).int)).char
    i32: int32 = (rand(high(int32).int)).int32
    i64: int64 = (rand(high(int64).int)).int64
    f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rand(high(int64).int)).float64
    b: bool = rand(1).bool

proc a() =
  var
    ui_i1 {.noinit.}: int
    ui_i2 {.noinit.}: array[3, int]
  echo "Value of uninitialized variable 'ui_i1': ", ui_i1
  echo "Value of uninitialized variable 'ui_i2': ", ui_i2

randomize(123) # arbitarily picked seed
polluteStack()
a()
Code Snippet 8: noinit pragma works for local variables
Value of uninitialized variable 'ui_i1': 123
Value of uninitialized variable 'ui_i2': [3458916362389291184, 805341723, 8863084066665201664]
Earlier confusion
Even for local vars, if the vars were not arrays, I was unable to have them echo with random values with the noinit pragma. Nim Issue #7852 has some interesting code snippets that behaved differently wrt noinit between me and data-man.
Fix
<2018-05-23 Wed> Above issue got fixed once I started using the polluteStack proc.
Examples of uninitialized arrays #

Here are some more examples of uninitialized local variables of array types:

import random, math
proc polluteStack() =
  var
    c: char = (rand(high(char).int)).char
    i32: int32 = (rand(high(int32).int)).int32
    i64: int64 = (rand(high(int64).int)).int64
    f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rand(high(int64).int)).float64
    b: bool = rand(1).bool

proc a() =
  var
    ai {.noinit.}: array[3, int]
    ac {.noinit.}: array[3, char]
    ab {.noinit.}: array[3, bool]
    af {.noinit.}: array[3, float]
  echo ai
  echo ac
  echo ab
  echo af

randomize(123) # arbitarily picked seed
polluteStack()
a()
[3458916362389291184, 805341723, 8863084066665201664]
['\x97', '\xB4', '\xDE']
[true, true, true]
[7.29112201955677e-304, 4.940656458412465e-324, 7.875093343280577e+18]
Earlier confusion
If I commented out the int array declaration in the above code (of course, its echo too), the char and bool arrays would start auto-initializing, but not the float.
Fix
<2018-05-23 Wed> Above issue got fixed once I started using the polluteStack proc. The confusion was created because the stack was clean.. needed something to pollute the stack first.
Scalar local vars and noinit #
Earlier confusion
Looks like noinit works only for the float64 var.
Fix
<2018-05-23 Wed> Above issue got fixed once I started using the polluteStack proc.

ref

import random, math
proc polluteStack() =
  var
    c: char = (rand(high(char).int)).char
    i32: int32 = (rand(high(int32).int)).int32
    i64: int64 = (rand(high(int64).int)).int64
    f32: float32 = (rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rand(high(int64).int)).float64
    b: bool = rand(1).bool

proc bar() =
  var
    i32: int32
    i64: int64
    f32: float32
    f64: float64

    i32ni {.noInit.}: int32
    i64ni {.noInit.}: int64
    f32ni {.noInit.}: float32
    f64ni {.noInit.}: float64

  echo "i32 (auto-init) = ", i32
  echo "i64 (auto-init) = ", i64
  echo "f32 (auto-init) = ", f32
  echo "f64 (auto-init) = ", f64

  echo "i32ni (no init) = ", i32ni
  echo "i64ni (no init) = ", i64ni
  echo "f32ni (no init) = ", f32ni
  echo "f64ni (no init) = ", f64ni

randomize(123) # arbitarily picked seed
polluteStack()
bar()
i32 (auto-init) = 0
i64 (auto-init) = 0
f32 (auto-init) = 0.0
f64 (auto-init) = 0.0
i32ni (no init) = 807869368
i64ni (no init) = 3469772516323406999
f32ni (no init) = 5.746757700654961e-35
f64ni (no init) = 1.421349011415116e+139

Types #

Arrays and Sequences #

Array and Sequence Types

Arrays #

  • Arrays are a homogeneous type, meaning that each element in the array has the same type.
  • Arrays always have a fixed length which has to be specified at compile time (except for open arrays).

The array type specification needs 2 things: size of the array, and the type of elements contained in that array using array[SIZE, TYPE].

The SIZE can be specified as a range like N .. M, or (M-N)+1 for short. The below example shows how an array of 6 integers can be declared and assigned using two different methods.

let
  i_arr1: array[0 .. 5, int] = [1, 2, 3, 4, 5, 6]
  i_arr2: array[6, int] = [7, 8, 9, 10, 11, 12]
echo i_arr1
echo i_arr2
[1, 2, 3, 4, 5, 6]
[7, 8, 9, 10, 11, 12]

The [ .. ] portion to the right of the assign operator (=) is called the array constructor.

  • Built-in procs low() and high() return the lower and upper bounds of an array.
    • Arrays can also have a non-zero starting index; see the below example.
  • len() returns the array length.
let
  i_arr: array[6 .. 11, int] = [1, 2, 3, 4, 5, 6]
echo "min_index = ", low(i_arr), ", max_index = ", high(i_arr), ", i_arr = ", i_arr, ", length = ", len(i_arr)
min_index = 6, max_index = 11, i_arr = [1, 2, 3, 4, 5, 6], length = 6
“Typed” Arrays #

Now, if you need to create a lot of arrays of the type array[6 .. 11, int], updating their size and/or element type can become painful and error-prone. So the convention is to first create a type for arrays, and then declare variables using that custom type.

Below example is a re-written version of the above example using “typed” arrays.

type
  IntArray = array[6 .. 11, int]
let
  i_arr: IntArray = [1, 2, 3, 4, 5, 6]
echo "min_index = ", low(i_arr), ", max_index = ", high(i_arr), ", i_arr = ", i_arr, ", length = ", len(i_arr)
min_index = 6, max_index = 11, i_arr = [1, 2, 3, 4, 5, 6], length = 6
Inferred Array Types #

Just as you can do let foo = "abc" instead of let foo: string = "abc", you can let Nim infer the array types too.

import strformat, typetraits
let
  arr1 = [1.0, 2, 3, 4]
  arr2 = ['a', 'b', 'c', 'd']
  arr3 = ["a", "bc", "def", "ghij", "klmno"]
echo fmt"arr1 is of type {arr1.type.name} with value {arr1}"
echo fmt"arr2 is of type {arr2.type.name} with value {arr2}"
echo fmt"arr3 is of type {arr3.type.name} with value {arr3}"
arr1 is of type array[0..3, float64] with value [1.0, 2.0, 3.0, 4.0]
arr2 is of type array[0..3, char] with value ['a', 'b', 'c', 'd']
arr3 is of type array[0..4, string] with value ["a", "bc", "def", "ghij", "klmno"]
Array elements need to be of the same type #

In the above example, let arr1 = [1.0, 2, 3, 4] worked because Nim inferred 2, 3 and 4 to be floats 2.0, 3.0 and 4.0.

But if you try to mix-and-match types in array elements where they cannot get coerced to the same type, you get an error.

let
  arr1 = [1.0, 2, 3, 4, 'a']
nim_src_PZd0Ji.nim(5, 25) Error: type mismatch: got <char> but expected 'float64 = float'
Two dimensional Arrays #

See Operators for an example of 2-D arrays.

Sequences #

Sequences are similar to arrays but of dynamic length which may change during runtime (like strings).

  • In the sequence constructor @[1, 2, 3], the [] portion is actually the array constructor, and @ is the array to sequence operator.
  • Just like other Nim assignments, the sequence type does not need to be specified if it is directly assigned a value — the sequence type is inferred by Nim in that case. See Specifying Types for more information.
import strformat, typetraits
let
  i_seq1:seq[int] = @[1, 2, 3, 4, 5, 6]
  i_seq2 = @[7.0, 8, 9]
echo fmt"i_seq1 is of type {i_seq1.type.name} with value {i_seq1}"
echo fmt"i_seq2 is of type {i_seq2.type.name} with value {i_seq2}"
i_seq1 is of type seq[int] with value @[1, 2, 3, 4, 5, 6]
i_seq2 is of type seq[float64] with value @[7.0, 8.0, 9.0]
  • One can append elements to a sequence with the add() proc or the & operator,
  • pop() can be used to remove (and get) the last element of a sequence.
  • Built-in proc high() returns the upper bound of a sequence.
    • low() also works for a sequence, but it will always return 0.
  • len() returns the sequence length.
import strformat, typetraits
var i_seq = @[1, 2, 3]
echo fmt"i_seq is of type {i_seq.type.name} with value {i_seq}"
echo fmt"i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"
echo "Adding 100 using `add' .."
i_seq.add(100)
echo fmt"  i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"
echo "Adding 200 using `&' .."
i_seq = i_seq & 200
echo fmt"  i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"
echo "Popping the last seq element using `pop' .."
let popped_elem = i_seq.pop
echo fmt"  popped_elem = {popped_elem}"
echo fmt"  i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"
i_seq is of type seq[int] with value @[1, 2, 3]
i_seq = @[1, 2, 3], length = 3, last elem = 3
Adding 100 using `add' ..
  i_seq = @[1, 2, 3, 100], length = 4, last elem = 100
Adding 200 using `&' ..
  i_seq = @[1, 2, 3, 100, 200], length = 5, last elem = 200
Popping the last seq element using `pop' ..
  popped_elem = 200
  i_seq = @[1, 2, 3, 100], length = 4, last elem = 100

Open Arrays #

Open arrays

Open array types can be assigned to only parameters of procedures that receive values of seq or array types.

  • Open array types are like dynamic arrays, and need to be specified with only the type of the elements that the array is going to contain.
  • This array type can be used only for parameters in procedures.
    • So if you do var foo: openArray[int] or let foo: openArray[int] = [1,2,3] (inside a proc or outside), you will get Error: invalid type: ‘openarray[int]’ for var or Error: invalid type: ‘openarray[int]’ for let.
  • The openArray type cannot be nested: multidimensional openarrays are not supported.
  • Built-in proc high() returns the upper bound of an open array too.
    • low() also works for an open array, but it will always return 0.
  • len() returns the open array length.
import strformat, typetraits
proc testOpenArray(x: openArray[int]) =
  echo fmt"x = {x} (type: {x.type.name}, length = {x.len}, max index = {x.high}"

let
  some_seq = @[1, 2, 3]
  some_arr = [1, 2, 3]
echo "Passing a seq .."
testOpenArray(some_seq)
echo "Passing an array .."
testOpenArray(some_arr)
Passing a seq ..
x = [1, 2, 3] (type: openarray[int], length = 3, max index = 2
Passing an array ..
x = [1, 2, 3] (type: openarray[int], length = 3, max index = 2

If using a Nim devel version older than b4626a220b, use echo repr(x) to print an open array x — plain old echo x or fmt like in the above example won’t work. See $ is not implemented for open arrays for details.

Open arrays serve as a convenience type for procedure parameters so that:

  • We don’t need to explicitly mention the types of the passed in array (or sequence) values.
    • For example, you can have a proc parameter of type openArray[int], and have it accept all of these types – array[5, int], array[1000, int], seq[int].. just that the array|seq|openArray elements need to be of the exact same type.. int in this case.
  • The proc can receive an array of any length as long as they contain the same type elements as defined in the openArray[TYPE].

Thanks to r3d9u11 from Nim Forum for the below example (ref).

type MyArr = array[3, int]
let
  a3: MyArr = [10, 20, 30]
  a4 = [10, 20, 30, 40]

# Compiler doesn't allow passing arrays with any other length
proc countMyArr(a: MyArr): int =
  for i in 0 .. a.high:
    result += a[i]
echo "countMyArray(a3) = ", countMyArr(a3)
# countMyArray(a4) # will fail

# Compiler doesn't allow passing arrays with any other length
proc countExplicitArrayType(a: array[3, int]): int =
  for i in 0 .. a.high:
    result += a[i]
echo "countExplicitArrayType(a3) = ", countExplicitArrayType(a3)
# countExplicitArrayType(a4) # will fail

# Compiler allows passing arrays with different lengths
proc countOpenArray(a: openArray[int]): int =
  for i in 0 .. a.high:
    result += a[i]
echo "countOpenArray(a3) = ", countOpenArray(a3)
echo "countOpenArray(a4) = ", countOpenArray(a4)
countMyArray(a3) = 60
countExplicitArrayType(a3) = 60
countOpenArray(a3) = 60
countOpenArray(a4) = 100

Above, you can see that countMyArr and countExplicitArrayType procs can accept only int arrays of length 3.

Attempting to pass the 4-element int array a4 to those will give an error like this:

nim_src_gHCsrO.nim(20, 61) Error: type mismatch: got <array[0..3, int]>
but expected one of:
proc countExplicitArrayType(a: array[3, int]): int

expression: countExplicitArrayType(a4)

But countOpenArray proc is not limited by the input array length — It can accept an int array of any length. So countOpenArray(a4) works just fine.

Open Array Limitations #
Modifying Open array variables inside procedures #

From my brief experimentation, you cannot modify the open arrays easily. For example, below does not work. I thought that as a passed in sequence got converted to an openarray type, the reverse should be possible too. But that’s not the case ..

proc foo(i_oa: openArray[int]) =
  var i_seq: seq[int] = i_oa
  i_seq.add(100)
  echo "input open array: ", i_oa
  echo "that open array modified to a sequence: ", i_seq
foo(@[10, 20, 30, 40])

Above gives the error:

nim_src_dqL4Jb.nim(5, 28) Error: type mismatch: got <openarray[int]> but expected 'seq[int]'

Here is a workaround:

proc foo(i_oa: openArray[int]) =
  var i_seq: seq[int]
  for i in i_oa: # copying the input openarray to seq element by element
    i_seq.add(i)
  i_seq.add(100)
  echo "input open array: ", i_oa
  echo "that open array modified to a sequence: ", i_seq
foo(@[10, 20, 30, 40])
input open array: [10, 20, 30, 40]
that open array modified to a sequence: @[10, 20, 30, 40, 100]
FIXED $ is not implemented for open arrays #

Thanks to @data-man from GitHub, this issue is now fixed in b4626a220b!

import strformat
proc foo(x: openArray[int]) =
  echo fmt"{x}"
foo([1, 2, 3])
[1, 2, 3]
  • Older failure

    As $ isn’t implement for open arrays, fmt wouldn’t work for these either – Nim Issue #7940.

    proc testOpenArray(x: openArray[int]) =
      echo x

    Above gives this error:

    nim_src_ghd2T5.nim(5, 8) Error: type mismatch: got <openarray[int]>
    but expected one of:
    proc `$`(x: string): string
    proc `$`(s: WideCString): string
    proc `$`[T: tuple |
        object](x: T): string
    proc `$`(x: uint64): string
    proc `$`(x: int64): string
    proc `$`[T, IDX](x: array[IDX, T]): string
    proc `$`[Enum: enum](x: Enum): string
    proc `$`(w: WideCString; estimate: int; replacement: int = 0x0000FFFD): string
    proc `$`[T](x: set[T]): string
    proc `$`[T](x: seq[T]): string
    proc `$`(x: int): string
    proc `$`(x: cstring): string
    proc `$`(x: bool): string
    proc `$`(x: float): string
    proc `$`(x: char): string
    
    expression: $(x)

    Until that gets fixed, the workaround is to use repr to print open arrays.

Seq vs Array vs Open Array #

Below are few useful comments from this Nim forum thread. While crediting the comment authors, I have taken the liberty to copy-edit those and add my own emphasis.

Stefan_Salewski from Nim Forum #

Seqs are generally fine when performance and code size is not critical. As seq data structure contains a pointer to the data elements, we have some indirection, which decreases performance.

Arrays are used when number of elements is known at compile time. They offer the maximum performance. Nim Arrays are value (i.e. not reference) objects, living on the stack (see Types, Heap and Stack) if used inside of procs. So a very direct element access is possible. Basically element access is only calculation of an offset, which is the product of element size and index value. If index is constant, then this offset is known at compile time, and so the access is as fast as that of a plain proc variable. An example is a chess program where we have the 64 fields. So we generally would use a array of 64 elements, not a seq. (Well, beginners may use a two dimensional array of 8 x 8, but then we have two indices, which again increases access time. A plain array is OK for this game, as we can increase index by one when we move a piece to the left, and increase index by 8 when we move forward.)

OpenArrays is just a proc parameter type that can accept arrays and seqs. Whenever a proc can be used with array or seq parameters, the openArray parameter type should be used. The word “OpenArray” is indeed a bit strange; it was used in the Wirthian languages Modula and Oberon, we have no better term currently.

r3d9u11 from Nim Forum #

(Provided a very useful example which I expanded upon in Open Arrays).

Types, Heap and Stack #

Credit
Below explanation is taken entirely from this comment by /u/PMunch from Reddit.

First order of business heap vs. stack: Whenever you call a function it creates a stack frame. Stacks are simply said first in last out, so when a function call is done it pops it’s frame from the stack, and you return to the previous frame. When you declare a variable inside a scope in is typically allocated on the stack. This means that when the function returns that part of memory is not in use anymore and the value is gone (technically it’s still there, but we shouldn’t try to access it).

The heap on the other hand is quite a bit different. Items on the heap are allocated and live there until you deallocate them. In C this is done manually with calls like malloc and free. In Nim however, and C# for that matter, we have the garbage collector (or GC), this nifty thing reads through our memory and checks if anything still points to things allocated on the heap and if nothing is pointing to that which was previously allocated it gets free’d. This means that we won’t leak memory as easily as you would in C as things that we’ve lost every reference to get’s automatically cleaned.

Okay, now that we know where our stuff lives in memory, let’s look at how Nim represents those things. Basic types are things like integers, floats, bools, and characters. Their size is known and static. Nim also calls strings a basic type, but those are not quite like the rest since they can change in size. Since our stack is first in last out it means that it makes sense to store everything in order. But storing things in order isn’t possible if you want to change the size of something (for example appending to a string). So when we create numbers in our Nim code they will be stored on the stack, this is why you never need to free your numbers and don’t have to set them to nil when you’re done with them.

So what are types? In Nim you can create aliases for types like type age = int this is just a way to say that age is an integer, and it will be treated like one for all intents and purposes. If we want to create collections of types to represent something particular we can create objects, don’t think of these quite like objects in an object oriented language, think of them more like structs in C. Such objects are simply a collection of values. So in Nim when we create an object it will live with us on the stack. If we return an object it will be copied to our caller, and if we insert it into a data structure it will also be copied. While this might be practical in many cases (even offering a speed benefit if done right) we often don’t want to copy our large objects around. This is when allocating on the heap comes into play.

If we define our type as a ref object it means that the type is actually a reference to an object. I’ll come back to the difference between a reference and a pointer later, but for now just remember that a reference is the memory location of an object on the heap. This means that if we return a ref object it means that we’re only returning the memory address of that object, not copying the object itself. This also means that if we insert it into a data structure only the reference to the object is inserted. Whenever you see new SomeObject it means that it allocates memory for that object on the heap, and gives us a reference to this object. If we had simply done var myObject: SomeObject and SomeObject was defined as a ref object we would only have a reference on our stack, so trying to access it would crash saying we had an “Illegal storage access”. This is because Nim defaults our value to nil, and no-one is allowed to access memory area 0.

So imagine we had an object that contained the height, the weight, and the age of a person. That could be represented by three integers. If we wanted to return this object it would mean copying all those three values to our caller. If we defined it as a reference, we would only pass one integer, the position in memory (on the heap) where we stored the three others. Conveniently this also means that if one functions modifies a value in a referenced object, that change would be visible for all other functions using the same reference (since they all point to the same place in memory). This is practical for example if you want to make one list sorted by age, one by height, and the third by weight. Instead of copying our person three times, we could just use three references, one in each list.

So now that we know where and how are values are stored we can look at the difference between a pointer and a reference. In pure Nim code you would typically only use references, these are what every call to new creates and what goes on behind the scenes most of the time when working with strings. A reference is also called a managed pointer, it simply means that Nim manages this area of memory, and that it will be automatically free’d for us by the garbage collector when Nim sees that we’re not using it any longer. Pointers on the other hand are unmanaged meaning that Nim doesn’t try to do anything with the memory behind that pointer, most of the time you won’t even know what is there. The reason there are pointers in Nim is mostly to interface with C. In C every time you want objects on the heap you need to manually malloc and free them. Many C libraries work by passing around a pointer to a structure containing some state and all good libraries have some way of dealing with this memory. Typically you do it by calling some initialisation function when you begin and then some cleanup function when you are done. In Nim we might want to use a C library such as that, but since we might loose the reference in our own code while the library still keeps a reference somewhere which Nim doesn’t know about we can’t have a reference to it as Nim would garbage collect it. So instead we have pointers.

Python-like Dictionaries #

Tuples #

A tuple type defines various named fields and an order of the fields. The constructor () can be used to construct tuples. The order of the fields in the constructor must match the order in the tuple’s definition.

The assignment operator for tuples copies each component.

Defining tuples #

  • Assigning the tuple type directly, anonymously. For anonymous tuples don’t use the tuple keyword and square brackets.

    let person: (string, int) = ("Peter", 30)
    echo person
    (Field0: "Peter", Field1: 30)
  • Assigning the tuple type directly, with named fields.

    let person: tuple[name: string, age: int] = ("Peter", 30)
    echo person
    (name: "Peter", age: 30)
  • First defining a custom tuple type, and then using that. This is useful if you don’t wan’t to copy/paste a verbose tuple type like tuple[name: string, age: int] at multiple places.. that approach is also very error-prone and painful if you ever need to refactor that tuple type throughout your code.

    type Person = tuple[name: string, age: int]
    let
      person1: Person = (name: "Peter", age: 30)
      # skipping the field names during assignment works too, but this could be less
      # readable.
      person2: Person = ("Mark", 40)
    echo person1
    echo person2
    (name: "Peter", age: 30)
    (name: "Mark", age: 40)
  • Alternative way of defining that same custom tuple type.

    type
      Person = tuple
        name: string
        age: int
    let person: Person = (name: "Peter", age: 30)
    echo person
    (name: "Peter", age: 30)

Accessing tuple fields #

  • The notation t.field is used to access a named tuple’s field.
  • Another notation is t[i] to access the i‘th field. Here i must be a constant integer. This works for both anonymous and named tuples.
Anonymous tuples #
import strformat, typetraits
let person: (string, int) = ("Peter", 30)
echo fmt"Tuple person of type {person.type.name} = {person}"
echo person[0]
echo person[1]
Tuple person of type (string, int) = (Field0: "Peter", Field1: 30)
Peter
30
Named tuples #
import strformat, typetraits
type
  Person = tuple
    name: string
    age: int
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {name(person.type)} = {person}"
echo person[0]
echo person.name
echo person[1]
echo person.age
Tuple person of type Person = (name: "Peter", age: 30)
Peter
Peter
30
30

See Clash between type.name and a custom tuple field name on why I am using name(person.type) in the above code snippet instead of person.type.name.

Tuple unpacking #

Tuples can be unpacked during variable assignment (and only then!). This can be handy to assign directly the fields of the tuples to individually named variables.

An example of this is the splitFile proc from the os module which returns the directory, name and extension of a path at the same time.

For tuple unpacking to work you must use parentheses around the values you want to assign the unpacking to, otherwise you will be assigning the same value to all the individual variables!

For example:

import os

let
  path = "usr/local/nimc.html"
  (dir, name, ext) = splitFile(path)
  baddir, badname, badext = splitFile(path)
echo "dir  = ", dir
echo "name = ", name
echo "ext  = ", ext
echo "baddir  = ", baddir
echo "badname = ", badname
echo "badext  = ", badext
dir  = usr/local
name = nimc
ext  = .html
baddir  = (dir: "usr/local", name: "nimc", ext: ".html")
badname = (dir: "usr/local", name: "nimc", ext: ".html")
badext  = (dir: "usr/local", name: "nimc", ext: ".html")

Tuple type equality #

Different tuple-types are equivalent if they:

  1. specify fields of the same type, and
  2. of the same name in the same order.
type Person = tuple[name: string, age: int]
var person: Person
person = (name: "Peter", age: 30)
echo "person = ", person

var teacher: tuple[name: string, age: int] = ("Mark", 42)
echo "teacher = ", teacher

# The following works because the field names and types are the same.
person = teacher
echo "person = ", person
person = (name: "Peter", age: 30)
teacher = (name: "Mark", age: 42)
person = (name: "Mark", age: 42)

Even though you don’t need to declare a type for a tuple to use it, tuples created with different field names will be considered different objects despite having the same field types.

var person: tuple[name: string, kids: int] = ("Peter", 1)
let teacher: tuple[name: string, age: int] = ("Mark", 42)
person = teacher

Above will give this error:

nim_src_KDTO15.nim(6, 8) Error: type mismatch: got <tuple[name: string, age: int]> but expected 'tuple[name: string, kids: int]'

From the following example though, it looks like anonymous tuples are considered to be of the same type as named tuples if:

  • they have the same number of fields, and
  • with the same type and order.
import strformat, typetraits
type Person = tuple[name: string, age: int]
let personAnon1 = ("Peter", 30)
var
  personNamed: Person
  personAnon2: (string, int)
echo fmt"Tuple personAnon1 of type {personAnon1.type.name} = {personAnon1}"

echo "Assigning an anonymous tuple to a named tuple .."
personNamed = personAnon1
echo fmt"Tuple personNamed of type {name(personNamed.type)} = {personNamed}"

echo "Assigning a named tuple to an anonymous tuple .."
personAnon2 = personNamed
echo fmt"Tuple personAnon2 of type {personAnon2.type.name} = {personAnon2}"
Tuple personAnon1 of type (string, int) = (Field0: "Peter", Field1: 30)
Assigning an anonymous tuple to a named tuple ..
Tuple personNamed of type Person = (name: "Peter", age: 30)
Assigning a named tuple to an anonymous tuple ..
Tuple personAnon2 of type (string, int) = (Field0: "Peter", Field1: 30)

When declaring new tuple variables by direct value assignment, don’t forget to specify the tuple type! If you don’t do so, that tuple will default to an anonymous tuple.

From the above example, note that person1 didn’t get the field names. But all other person* variables did.

TODO Objects #

TODO Ref Objects #

TODO new keyword #

Command line parameters #

  • paramCount returns the number of command line parameters given to the application.
  • commandLineParams returns the command line parameters.
  • Do import os before using any of the above functions.
# Arguments passed: foo bar "zoo car"
import os
let
  numParams = paramCount()
  params = commandLineParams()
echo "Number of command line params: ", numParams
echo "Command line params: ", params
echo "Param 1 = ", params[0]
echo "Param 2 = ", params[1]
echo "Param 3 = ", params[2]
Number of command line params: 3
Command line params: @["foo", "bar", "zoo car"]
Param 1 = foo
Param 2 = bar
Param 3 = zoo car

For serious longopt and shortopt command line parameter parsing using the cligen library.

Terminal #

Hello World animation #

Credit
Below code is taken from this comment by /u/psychotic_primes from Reddit.
import os, random, strutils, terminal

randomize() # Initialize the RNG with a new seed each run
hideCursor() # Hide the cursor during runtime

var target, output: string

if paramCount() >= 1:
  target = paramStr(1)
else:
  target = "Hello, world"
output = spaces(target.len)

for i, x in target:
  while x != output[i]:
    output[i] = chr(rand(32 .. 126))
    # Instead of writing a bunch of new lines, we can just write to the same
    # line repeatedly.
    eraseLine()
    stdout.write(output)
    stdout.flushFile() # Force writing to console every iteration
    sleep(3) # milliseconds
# Add a new line at the end of program execution to keep the terminal tidy.
echo ""
showCursor() # Bring the cursor back

Shell #

Environment Variables #

  • Use putEnv to set environment variable
  • Use getEnv to get environment variable
import os
let
  envVar = "NIM_TEMP"
  envVarVal = "foo"
putEnv(envVar, envVarVal)
echo getEnv(envVar)
foo

File paths #

File base name #

import os
let filePath = "tests/test1.org"
var (dir, basename, ext) = splitFile(filePath)
echo "dir = ", dir
echo "basename = ", basename
echo "ext = ", ext
dir = tests
basename = test1
ext = .org

File handling #

Writing files #

let
  fileName = "/tmp/foo/bar/zoo.txt"
  dataStr = "abc"
writeFile(fileName, dataStr)

The directory containing the fileName in the above example has to exist, else you get the IOError exception:

Error: unhandled exception: cannot open: /tmp/foo/bar/zoo.txt [IOError]

If you want to force-create a non-existing directory, you can do:

import os, strformat
let
  fileName = "/tmp/foo/bar/zoo.txt"
  dataStr = "abc"
  (dir, _, _) = splitFile(fileName)
echo fmt"{dir} exists? {dirExists(dir)}"
if (not dirExists(dir)):
  echo fmt"  creating {dir} .."
  createDir(dir)
echo fmt"{dir} exists now? {dirExists(dir)}"
writeFile(fileName, dataStr)
removeDir("/tmp/foo/bar/")
/tmp/foo/bar exists? true
/tmp/foo/bar exists now? true

File Permissions #

Get/read file permissions #

import os
let
  fileName = "/tmp/foo/bar/zoo.txt"
  dataStr = "abc"
  (dir, _, _) = splitFile(fileName)

if (not dirExists(dir)):
  createDir(dir)
writeFile(fileName, dataStr)
let filePerm = getFilePermissions(fileName)
echo filePerm
{fpUserWrite, fpUserRead, fpGroupRead, fpOthersRead}

Octal string to set[FilePermission] #

import os
proc parseFilePermissions(octals: string): set[FilePermission] =
  ## Converts the input permissions octal string to a Nim set for FilePermission type.
  # https://devdocs.io/nim/os#FilePermission
  var perm: set[FilePermission]
  let
    readPerms = @[fpUserRead, fpGroupRead, fpOthersRead]
    writePerms = @[fpUserWrite, fpGroupWrite, fpOthersWrite]
    execPerms = @[fpUserExec, fpGroupExec, fpOthersExec]
  for idx, o in octals:
    if o != '0':
      if o in {'4', '5', '6', '7'}:
        perm = perm + {readPerms[idx]}
      if o in {'2', '3', '6', '7'}:
        perm = perm + {writePerms[idx]}
      if o in {'1', '3', '5', '7'}:
        perm = perm + {execPerms[idx]}
  result = perm

import random, strformat
randomize(1)
for _ in 0 .. 10:
  let perm = fmt"{rand(7)}{rand(7)}{rand(7)}"
  echo perm, " = ", parseFilePermissions(perm)
112 = {fpUserExec, fpGroupExec, fpOthersWrite}
114 = {fpUserExec, fpGroupExec, fpOthersRead}
436 = {fpUserRead, fpGroupExec, fpGroupWrite, fpOthersWrite, fpOthersRead}
752 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupExec, fpGroupRead, fpOthersWrite}
727 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupWrite, fpOthersExec, fpOthersWrite, fpOthersRead}
101 = {fpUserExec, fpOthersExec}
236 = {fpUserWrite, fpGroupExec, fpGroupWrite, fpOthersWrite, fpOthersRead}
522 = {fpUserExec, fpUserRead, fpGroupWrite, fpOthersWrite}
425 = {fpUserRead, fpGroupWrite, fpOthersExec, fpOthersRead}
273 = {fpUserWrite, fpGroupExec, fpGroupWrite, fpGroupRead, fpOthersExec, fpOthersWrite}
304 = {fpUserExec, fpUserWrite, fpOthersRead}

Exceptions #

Exception Hierarchy #

https://devdocs.io/nim/manual#exception-handling-exception-hierarchy

All exceptions are objects of type Exception.

  • Exception
    • AccessViolationError
    • ArithmeticError
      • DivByZeroError
      • OverflowError
    • AssertionError
    • DeadThreadError
    • FloatingPointError
      • FloatDivByZeroError
      • FloatInexactError
      • FloatInvalidOpError
      • FloatOverflowError
      • FloatUnderflowError
    • FieldError
    • IndexError
    • ObjectAssignmentError
    • ObjectConversionError
    • ValueError
      • KeyError
    • ReraiseError
    • RangeError
    • OutOfMemoryError
    • ResourceExhaustedError
    • StackOverflowError
    • SystemError
      • IOError
      • OSError
        • LibraryError

Custom Exceptions #

ref

type
  MyError = object of Exception

Raising Exceptions #

type
  MyError = object of Exception
raise newException(MyError, "details about what went wrong")

Evaluating above will give:

Error: unhandled exception: details about what went wrong [MyError]

Handling/catching Exceptions #

Above example had unhandled exceptions. You must always handle (my rule) exceptions and do something about it; even if it means simply printing out the exception error using getCurrentExceptionMsg().

type
  MyError = object of Exception
try:
  raise newException(MyError, "details about what went wrong")
except MyError:
  stderr.writeLine "Error: ", getCurrentExceptionMsg()
  quit 1 # Quit with error exit code

Evaluating above will print this on stderr:

Error: details about what went wrong

getCurrentExceptionMsg() returns only the msg parameter from the Exception object. If you need to get the Exception name too, use getCurrentException() instead, which return a value of type Exception, and then print its name as shown in the below example.

import strformat
type
  MyError = object of Exception
try:
  raise newException(MyError, "details about what went wrong")
except MyError:
  echo fmt"[Error] {getCurrentException().name}: {getCurrentException().msg}"
[Error] MyError: details about what went wrong

Rules of Thumb (learnt from Nimisms) #

As I am collecting these rules for myself, I am seeing a trend.. a single space can break Nim!

Don’t add space before array constructors! #

Thanks to @data-man from GitHub for this tip.

Nim Issue #7840

var foo: array [0 .. 4, bool]
echo foo

Above gives:

nim_src_6ylaig.nim(4, 5) Error: invalid type: 'T' in this context: 'array' for var

But remove that space before the [ (aka the array constructor), and it works!

var foo: array[0 .. 4, bool]
echo foo
[false, false, false, false, false]

Always surround = sign with spaces #

While below works:

let foo=true
echo foo
true

Below doesn’t!

let foo=@["abc", "def"]
echo foo

You will get this error:

int: system [Processing]
Hint: nim_src_kowItE [Processing]
nim_src_kowItE.nim(4, 10) Error: ':' or '=' expected, but found '['
/bin/sh: /tmp/babel-wQPYTr/nim_src_kowItE: Permission denied

But once you surround that = with spaces, it will work. So always do that.

let foo: seq[string] = @["abc", "def"]
echo foo
@["abc", "def"]

Always use spaces before and after .. and ..< #

Nim Issue #6216

var str = "abc"
echo str[0 .. str.high]
abc
var str = "abc"
echo str[0 ..< str.high]
ab

ref

No space between proc identifier and list of args when num args >= 2 #

Below is a simple proc definition and invocation.. it works.

proc foo(a: int, b: int) =
  echo a, " ", b
foo(123, 456)
123 456

But see what happens when I add a space before the opening parenthesis in foo(123, 456):

proc foo(a: int, b: int) =
  echo a, " ", b
foo (123, 456)
nim_src_Uewd95.nim(6, 1) Error: type mismatch: got <tuple of (int, int)>
but expected one of:
proc foo(a: int; b: int)

expression: foo (123, 456)

With that space, Nim thinks that we are passing a tuple (123, 456) to foo proc!

So no space between proc identifier and list of arguments!

TODO Avoid using auto as proc return types #

From this comment by @GULPF from GitHub:

You could simplify the proc signature by having separate procs for float32/float64. IMO auto is a code smell and should be avoided, it just makes things harder to understand.

Context:

proc log*[X, B: SomeFloat](x: X, base: B): auto =
  ## Computes the logarithm ``base`` of ``x``
  when B is float64 or X is float64:
    var r: float64
  else:
    var r: float32
  r = ln(x) / ln(base)
  return r

TODO Update this section with the canonical way to write the above function #

Nimisms #

TO BE FIXED Picky about spaces before array constructor #

<2018-05-18 Fri>

Below array declaration is from the manual:

var a {.noInit.}: array [0 .. 1023, char]

gives this error (Nim Issue #7840):

Hint: system [Processing]
Hint: nim_src_sSD4Jx [Processing]
nim_src_sSD4Jx.nim(4, 5) Error: invalid type: 'T' in this context: 'array' for var

Workaround.. don’t put any space before that [ #

var a {.noInit.}: array[0 .. 1023, char]

TO BE FIXED Subrange definition with ..< #

<2018-05-17 Thu>

While the rule in section Always use spaces before and after .. and ..< applies in general, the same still does not work for the example in Nim Issue #6788.

const size = 6
type A = range[0 ..< size]

Trying to compile above gives this error:

nim_src_bshJJ4.nim(5, 10) Error: range types need to be constructed with '..', '..<' is not supported

Workaround #

const size = 6
type A = range[0 .. (size-1)]

FIXED Echoing sequences #

Nim Issue #6225

This issue has been fixed.<2018-05-17 Thu>

Now fixed #

let seq1 = @[1, 2, 3]
echo "Integer elements: ", seq1
let seq2 = @['1', '2', '3']
echo "Char elements: ", seq2
let seq3 = @["1", "2", "3"]
echo "String elements: ", seq3
Integer elements: @[1, 2, 3]
Char elements: @['1', '2', '3']
String elements: @["1", "2", "3"]

Equivalent code in Python (sort of, because Python does not have char vs string):

list1 = [1, 2, 3]
print('Integer elements: {}'.format(list1))
list2 = ['1', '2', '3']
print('String elements: {}'.format(list2))
Integer elements: [1, 2, 3]
String elements: ['1', '2', '3']

Old issue #

Nim Issue #6225

Unable to distinguish the seq element types from echo outputs.

let seq1 = @[1, 2, 3]
echo "Integer elements: ", seq1
let seq2 = @['1', '2', '3']
echo "Char elements: ", seq2
let seq3 = @["1", "2", "3"]
echo "String elements: ", seq3
Integer elements: @[1, 2, 3]
Char elements: @[1, 2, 3]
String elements: @[1, 2, 3]

Same as above, but using .repr:

let seq1 = @[1, 2, 3]
echo "Integer elements (Repr): ", seq1.repr
let seq2 = @['1', '2', '3']
echo "Char elements (Repr): ", seq2.repr
let seq3 = @["1", "2", "3"]
echo "String elements (Repr): ", seq3.repr
Integer elements (Repr): 0x7f4c87d8e048[1, 2, 3]

Char elements (Repr): 0x7f4c87d90048['1', '2', '3']

String elements (Repr): 0x7f4c87d8e0b8[0x7f4c87d900f8"1", 0x7f4c87d90120"2", 0x7f4c87d90148"3"]

Equivalent code in Python (sort of, because Python does not have char vs string):

list1 = [1, 2, 3]
print('Integer elements: {}'.format(list1))
list2 = ['1', '2', '3']
print('String elements: {}'.format(list2))
Integer elements: [1, 2, 3]
String elements: ['1', '2', '3']
Improving the default echo proc, a new proc debug #

<2017-12-13 Wed> This debug proc is not needed, once Nim PR #6825 gets merged.

import macros
import sequtils
import strutils
import future

proc toDebugRepr[T](x: T): string =
  when T is seq:
    result = "@[" & x.map(el => toDebugRepr(el)).join(", ") & "]"
  elif T is array:
    result = "[" & x.map(el => toDebugRepr(el)).join(", ") & "]"
  elif T is string:
    result = "\"" & x & "\""
  elif T is char:
    result = "'" & x & "'"
  else:
    result = $x

macro debug*(args: varargs[typed]): untyped =
  result = newCall(bindSym("echo"))
  for arg in args:
    result.add(newCall(bindSym("toDebugRepr"), arg))

debug 1, 2, 3
debug 123
debug "hello world"
debug "a", "b"
debug(@[1, 2, 3])
debug(@["1", "2", "3"])
debug(@['1', '2', '3'])
debug([1, 2, 3])
debug(["1", "2", "3"])
debug(['1', '2', '3'])
let temp_seq: seq[string] = "1,2,3".split(',', maxsplit=1)
echo temp_seq
debug temp_seq
123
123
"hello world"
"a""b"
@[1, 2, 3]
@["1", "2", "3"]
@['1', '2', '3']
[1, 2, 3]
["1", "2", "3"]
['1', '2', '3']
@[1, 2,3]
@["1", "2,3"]
Python print for comparison #
print(1, 2, 3)
print("hello world")
print("a", "b")
print([1, 2, 3])
print(["1", "2", "3"])
1 2 3
hello world
a b
[1, 2, 3]
['1', '2', '3']

FIXED Inconsistency in parentheses requirement after echo #

Nim Issue #6210

This issue has been fixed.

import strutils
echo "Sequences:"
echo @["a", "b", "c"].join(" ")
echo join(@["a", "b", "c"], " ")
echo "Lists:"
echo (["a", "b", "c"].join(" ")) # Works!
echo join(["a", "b", "c"], " ") # Works!
echo ["a", "b", "c"].join(" ") # This did not use to work, now works! -- Mon Oct 16 11:03:52 EDT 2017 - kmodi
var list = ["a", "b", "c"]
echo list.join(" ") # Works too!
Sequences:
a b c
a b c
Lists:
a b c
a b c
a b c
a b c

Modules/Packages/Libraries #

parsetoml #

This is an external library.

Installation #

nimble install parsetoml

Extracting all keys of a TOML table #

ref

import parsetoml

let
  cfg = parsetoml.parseString("""
[tmux]
  req_env_vars = ["STOW_PKGS_TARGET"]
  [tmux.set_env_vars]
    CFLAGS = "-fgnu89-inline -I${STOW_PKGS_TARGET}/include -I${STOW_PKGS_TARGET}/include/ncursesw"
    LDFLAGS = "-L${STOW_PKGS_TARGET}/lib"
""")
  pkg = "tmux"
let tmux = cfg.getTable(pkg)
for key, val in tmux.pairs:
  echo key
req_env_vars
set_env_vars
Older ways to do the same #
import parsetoml

let
  cfg = parsetoml.parseString("""
[tmux]
  req_env_vars = ["STOW_PKGS_TARGET"]
  [tmux.set_env_vars]
    CFLAGS = "-fgnu89-inline -I${STOW_PKGS_TARGET}/include -I${STOW_PKGS_TARGET}/include/ncursesw"
    LDFLAGS = "-L${STOW_PKGS_TARGET}/lib"
""")
  pkg = "tmux"
for key, val in cfg.pairs:
  if key == "tmux":
    for key, val in val.tableVal.pairs:
      echo key
req_env_vars
set_env_vars
import parsetoml

let
  cfg = parsetoml.parseString("""
[tmux]
  req_env_vars = ["STOW_PKGS_TARGET"]
  [tmux.set_env_vars]
    CFLAGS = "-fgnu89-inline -I${STOW_PKGS_TARGET}/include -I${STOW_PKGS_TARGET}/include/ncursesw"
    LDFLAGS = "-L${STOW_PKGS_TARGET}/lib"
""")
  pkg = "tmux"
let tmux = cfg.getValueFromFullAddr(pkg)
for key, val in tmux.tableVal.pairs:
  echo key
req_env_vars
set_env_vars

strformat #

This is a standard Nim library.

Refer to this separate set of notes that explains fmt (or &) from the strformat library in great detail, including examples for all options in the fmt format specifier.

bignum #

This is an external library.

Installation #

nimble install bignum

Examples #

import strformat, bignum
const upper = 61
var bn: array[upper, Rat]

proc bernoulli(n: int): Rat =
  var A: seq[Rat]

  for i in 0 .. n:
    A.add(newRat())

  for i in 0 .. A.high:
    discard A[i].set(1, i+1)
    for j in countDown(i, 1):
      A[j-1] = j*(A[j-1] - A[j])

  return A[0] # (which is Bn)

for i in 0 .. bn.high:
  bn[i] = bernoulli(i)
  if bn[i].toFloat != 0.0:
    echo fmt"B({i:2}) = {bn[i]:>55}"
B( 0) =                                                       1
B( 1) =                                                     1/2
B( 2) =                                                     1/6
B( 4) =                                                   -1/30
B( 6) =                                                    1/42
B( 8) =                                                   -1/30
B(10) =                                                    5/66
B(12) =                                               -691/2730
B(14) =                                                     7/6
B(16) =                                               -3617/510
B(18) =                                               43867/798
B(20) =                                             -174611/330
B(22) =                                              854513/138
B(24) =                                         -236364091/2730
B(26) =                                               8553103/6
B(28) =                                        -23749461029/870
B(30) =                                     8615841276005/14322
B(32) =                                      -7709321041217/510
B(34) =                                         2577687858367/6
B(36) =                           -26315271553053477373/1919190
B(38) =                                      2929993913841559/6
B(40) =                            -261082718496449122051/13530
B(42) =                             1520097643918070802691/1806
B(44) =                            -27833269579301024235023/690
B(46) =                            596451111593912163277961/282
B(48) =                     -5609403368997817686249127547/46410
B(50) =                          495057205241079648212477525/66
B(52) =                    -801165718135489957347924991853/1590
B(54) =                    29149963634884862421418123812691/798
B(56) =                 -2479392929313226753685415739663229/870
B(58) =                 84483613348880041862046775994036021/354
B(60) =   -1215233140483755572040304994079820246041491/56786730

typetraits #

This is a standard Nim library.

This module mainly defined the name proc which is used to get the string name of any type. It also defines $ aliased to name.

import typetraits

var x = 5
var y = "foo"

# Using regular proc call style
echo name(x.type), " x = ", x
echo name(y.type), " y = ", y

# Using UFCS
echo x.type.name, " x = ", x
echo y.type.name, " y = ", y

# Using $
echo $x.type, " x = ", x
echo $y.type, " y = ", y
int x = 5
string y = foo
int x = 5
string y = foo
int x = 5
string y = foo

Credit

TO BE FIXED Clash between type.name and a custom tuple field name #

Nim Issue #7975

Using $ works #
import strformat, typetraits
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {$person.type} = {person}"
Tuple person of type Person = (name: "Peter", age: 30)
Using name(VAR.type) works too #
import strformat, typetraits
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {name(person.type)} = {person}"
Tuple person of type Person = (name: "Peter", age: 30)
Using VAR.type.name FAILS #
import strformat, typetraits
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {person.type.name} = {person}"
Hint: typetraits [Processing]
nim_src_2f8wWD.nim(7, 9) template/generic instantiation from here
lib/pure/strformat.nim(260, 8) Error: type mismatch: got <string, type string>
but expected one of:
proc add(x: var string; y: string)
  first type mismatch at position: 2
  required type: string
  but expression 'type(person).name' is of type: type string
proc add(x: var string; y: char)
  first type mismatch at position: 2
  required type: char
  but expression 'type(person).name' is of type: type string
proc add(result: var string; x: int64)
  first type mismatch at position: 2
  required type: int64
  but expression 'type(person).name' is of type: type string
proc add(result: var string; x: float)
  first type mismatch at position: 2
  required type: float
  but expression 'type(person).name' is of type: type string
proc add(x: var string; y: cstring)
  first type mismatch at position: 2
  required type: cstring
  but expression 'type(person).name' is of type: type string
proc add[T](x: var seq[T]; y: openArray[T])
  first type mismatch at position: 1
  required type: var seq[T]
  but expression 'fmtRes218041' is of type: string
proc add[T](x: var seq[T]; y: T)
  first type mismatch at position: 1
  required type: var seq[T]
  but expression 'fmtRes218041' is of type: string

expression: add(fmtRes218041, type(person).name)
Changing the tuple field name to anything but “name” works (not practical) #

type.name works again if I change the tuple field name to anything but name:

import strformat, typetraits
type Person = tuple[namex: string, age: int]
let person: Person = ("Peter", 30)
echo fmt"Tuple person of type {person.type.name} = {person}"
Tuple person of type Person = (namex: "Peter", age: 30)

TO BE FIXED Tuple type name gets stuck to the first printed tuple var #

Nim Issue #7976

In the below examples:

  • person1 is a tuple variable of custom type Person.
  • person2 is another tuple variable of the same type, but it is not assigned the Person type explicitly, it is directly assigned the tuple[name: string, age: int] type.

In the below example, I am first printing person1 type and value, and then the same for person2.

import strformat, typetraits
type Person = tuple[name: string, age: int]
let
  person1: Person = ("Peter", 30)
  person2: tuple[name: string, age: int] = (name : "Peter", age : 30)
echo fmt"Tuple person1 of type {$person1.type} = {person1}"
echo fmt"Tuple person2 of type {$person2.type} = {person2}"
Tuple person1 of type Person = (name: "Peter", age: 30)
Tuple person2 of type Person = (name: "Peter", age: 30)

Above, I was expecting the person2 type to be printed as tuple[name: string, age: int].

Now, below I just switch the order of printing the same tuple variables.. this time I print person2 type and value first.

.. and now, the type name printed for both person1 and person2 is tuple[name: string, age: int]!

import strformat, typetraits
type Person = tuple[name: string, age: int]
let
  person1: Person = ("Peter", 30)
  person2: tuple[name: string, age: int] = (name : "Peter", age : 30)
echo fmt"Tuple person2 of type {$person2.type} = {person2}"
echo fmt"Tuple person1 of type {$person1.type} = {person1}"
Tuple person2 of type tuple[name: string, age: int] = (name: "Peter", age: 30)
Tuple person1 of type tuple[name: string, age: int] = (name: "Peter", age: 30)

Above, I was expecting the person1 type to be printed as Person.

tables #

This is a standard Nim library.

From ref:

import tables, typetraits

var a = {"hi": 1, "there": 2}.toTable
echo a["hi"], " ", a.len
echo "a is of type ", a.type.name
assert a.hasKey("hi")

for key, value in a:
  echo key, " " ,value
1 2
a is of type Table[system.string, system.int]
hi 1
there 2

From ref:

import tables

var
  hash = initTable[string, int]() # empty hash table
  hash2 = {"key1": 1, "key2": 2}.toTable # hash table with two keys
  hash3 = [("key1", 1), ("key2", 2)].toTable # hash table from tuple array
  hash4 = @[("key1", 1), ("key2", 2)].toTable # hash table from tuple seq
  value = hash2["key1"]

hash["spam"] = 1
hash["eggs"] = 2
hash.add("foo", 3)

echo "hash has ", hash.len, " elements"
echo "hash has key foo? ", hash.hasKey("foo")
echo "hash has key bar? ", hash.hasKey("bar")

echo "iterate pairs:" # iterating over (key, value) pairs
for key, value in hash:
  echo key, ": ", value

echo "iterate keys:" # iterating over keys
for key in hash.keys:
  echo key

echo "iterate values:" # iterating over values
for key in hash.values:
  echo key

# getting values of specified keys
echo """hash["spam"] = """, hash["spam"]
# If you try to get value for an unset key, you will get
# a KeyError exception.
try:
  echo """hash["key_not_set_yet"] = """, hash["key_not_set_yet"]
except KeyError:
  echo """hash["key_not_set_yet"] is not yet set."""
hash has 3 elements
hash has key foo? true
hash has key bar? false
iterate pairs:
eggs: 2
foo: 3
spam: 1
iterate keys:
eggs
foo
spam
iterate values:
2
3
1
hash["spam"] = 1
hash["key_not_set_yet"] is not yet set.

critbits #

This is a standard Nim library.

Crit-bit Trees were introduced by Adam Langley in this paper of his. This module is an implementation of that.

To me, the CritBitTree[T] type defined by critbits looks like a string-indexed dictionary/hash/table/associative-array-like type.

For brevity, now on I will refer to CritBitTree as CBT.

In the documentation, the CritBitTree[T] type is defined as:

CritBitTree[T] = object
  root: Node[T]
  count: int

where T is the type of the value held by each CritBitTree “key” (maybe it’s technically called a node?).

As I am more familiar with hashes and dictionaries, I’ll just refer to “whatever technically correct term for ‘key’ in CBT’s” as just key.

CBT Sets #

CBT’s can also be used in a non-dictionary/hash/table/associative-array fashion.. as just sets of the string keys. The type T for such CBT’s is void.

Now on, I will refer to such CBT’s, with T as void, as set-type or void-type.

The other category of CBT’s will be referred to as hash-type.

Initializing #

CBT objects auto-initialize.

import strformat, typetraits, critbits
var
  s: CritBitTree[void]
  t: CritBitTree[int]
echo fmt"{s.type.name} s (CBT as set) = {s}"
echo fmt"{t.type.name} t (CBT as hash) = {t}"
CritBitTree[system.void] s (CBT as set) = {}
CritBitTree[system.int] t (CBT as hash) = {:}
FIXED $ does not work for set-type CBT’s #

Nim Issue #7987 Fixed by @data-man from GitHub in aa7348b356.

Older issue #

Due to this bug, the echoing of the set-type CBT s in above example was commented out.

Uncommenting that line gave this error:

nim_src_saJsiR.nim(8, 9) template/generic instantiation from here
lib/pure/strformat.nim(268, 13) template/generic instantiation from here
lib/pure/collections/critbits.nim(325, 26) template/generic instantiation from here
lib/pure/collections/critbits.nim(244, 43) Error: undeclared field: 'val'

Keys #

Adding keys #
For hash-type CBT’s #

If your CBT variable t is of type CritBitTree[T], you assign a value to STRING key using t[STRING] = VAL, where VAL is of type T.

import strformat, typetraits, critbits
var t: CritBitTree[int]
t["a"] = 1
echo fmt"{t.type.name} t = {t}"
echo """Mutating or changing the value of t["a"] .. """
t["a"] = 2
echo fmt"{t.type.name} t = {t}"
CritBitTree[system.int] t = {"a": 1}
Mutating or changing the value of t["a"] ..
CritBitTree[system.int] t = {"a": 2}

Only for “int” CBT’s, you can even use the inc to set initial values (defaults to 1) for unassigned keys.

import critbits
var t: CritBitTree[int]
t.inc("a")
t.inc("b")
t.inc("c")
t.inc("d", 10)
echo t
{"a": 1, "b": 1, "c": 1, "d": 10}

Above code is equivalent to:

import critbits
var t: CritBitTree[int]
t["a"] = 1
t["b"] = 1
t["c"] = 1
t["d"] = 10
echo t
{"a": 1, "b": 1, "c": 1, "d": 10}

Keys can also be added using incl.

import critbits
var
  t1: CritBitTree[int]
  t2: CritBitTree[string]
t1.incl("a", 12)
t1.incl("b", 34)
echo t1
t1.incl("a", 77) # Overwrite the "a" key value
echo t1
t2.incl("a", "def")
t2.incl("b", "xyz")
echo t2
{"a": 12, "b": 34}
{"a": 77, "b": 34}
{"a": "def", "b": "xyz"}
  • FIXED Only the first key get initialized to 1; all others init to 0

    Nim Issue #7990

    Thanks to @data-man from GitHub for fixing this in 12f929e582.

    • Older Issue

      Earlier, the above output was:

      {"a": 1, "b": 0, "c": 0, "d": 0}
For set-type CBT’s #

The set-type CBT’s have only keys.. no values. So for these, you have to use the incl proc to add the keys or set elements.

import strformat, typetraits, critbits
var s: CritBitTree[void]
echo fmt"Type of s: {s.type.name}"
echo """Adding "a" .."""
incl(s, "a")
echo """Adding "b" .."""
s.incl("b")
echo fmt"  s has {s.len} elements: {s}"
echo """Adding "a" again .."""
s.incl("a")
echo fmt"  s *still* has {s.len} elements: {s}"
Type of s: CritBitTree[system.void]
Adding "a" ..
Adding "b" ..
  s has 2 elements: {"a", "b"}
Adding "a" again ..
  s *still* has 2 elements: {"a", "b"}
TODO Removing keys #
Key check #

Use contains or its alias hasKey.

import strformat, typetraits, critbits
var
  s: CritBitTree[void]
  t: CritBitTree[int]
s.incl("y")
t["a"] = 1
echo fmt"{s.type.name} s = {s}"
echo fmt"""s["x"] exists? {s.contains("x")}"""
echo fmt"""s["y"] exists? {s.hasKey("y")}"""
echo fmt"{t.type.name} t = {t}"
echo fmt"""t["a"] exists? {t.contains("a")}"""
echo fmt"""t["b"] exists? {t.hasKey("b")}"""
CritBitTree[system.void] s = {"y"}
s["x"] exists? false
s["y"] exists? true
CritBitTree[system.int] t = {"a": 1}
t["a"] exists? true
t["b"] exists? false
Number of keys #

Use len.

import strformat, typetraits, critbits
var
  s: CritBitTree[void]
  t: CritBitTree[string]
s.incl("foo")
s.incl("bar")
s.incl("zoo")
echo fmt"{s.type.name} s = {s}"
echo fmt"Number of elements in s = {s.len}"
t["a"] = "hello"
t["b"] = "world"
echo fmt"{t.type.name} t = {t}"
echo fmt"Number of elements in t = {t.len}"
CritBitTree[system.void] s = {"bar", "foo", "zoo"}
Number of elements in s = 3
CritBitTree[system.string] t = {"a": "hello", "b": "world"}
Number of elements in t = 2

Values #

Incrementing/Decrementing key values #

This works only for CBT’s of type CritBitTree[int].

Incrementing key values #
import strformat, typetraits, critbits
var t: CritBitTree[int]
t["a"] = 10
echo fmt"{t.type.name} t = {t}"
inc(t, "a")
echo fmt"{t.type.name} t = {t}"
t.inc("a")
echo fmt"{t.type.name} t = {t}"
CritBitTree[system.int] t = {"a": 10}
CritBitTree[system.int] t = {"a": 11}
CritBitTree[system.int] t = {"a": 12}
Decrementing key values #

The inc proc increments the key value by 1 by default. But if a negative “incrementing” value is provided, it can decrement too.

import strformat, typetraits, critbits
var t: CritBitTree[int]
echo fmt"{t.type.name} t = {t}"
echo """Initializing/incrementing "a" key to 1 .."""
t.inc("a")
echo fmt"  {t.type.name} t = {t}"
echo """Decrementing "a" key by 1 .."""
t.inc("a", -1)
echo fmt"  {t.type.name} t = {t}"
echo """Incrementing "a" key by 4 .."""
t.inc("a")
t.inc("a")
t.inc("a")
t.inc("a")
echo fmt"  {t.type.name} t = {t}"
echo """Decrementing "a" key by 2 .."""
t.inc("a", -1)
t.inc("a", -1)
echo fmt"  {t.type.name} t = {t}"
CritBitTree[system.int] t = {:}
Initializing/incrementing "a" key to 1 ..
  CritBitTree[system.int] t = {"a": 1}
Decrementing "a" key by 1 ..
  CritBitTree[system.int] t = {"a": 0}
Incrementing "a" key by 4 ..
  CritBitTree[system.int] t = {"a": 4}
Decrementing "a" key by 2 ..
  CritBitTree[system.int] t = {"a": 2}

Extending the above, you can increment/decrement by any value other than 1 too.

import strformat, typetraits, critbits
var t: CritBitTree[int]
echo fmt"{t.type.name} t = {t}"
t.inc("a", 5)
t.inc("b", 7)
echo fmt"{t.type.name} t = {t}"
t.inc("a", -10)
t.inc("b", -100)
echo fmt"{t.type.name} t = {t}"
CritBitTree[system.int] t = {:}
CritBitTree[system.int] t = {"a": 5, "b": 7}
CritBitTree[system.int] t = {"a": -5, "b": -93}
Retrieving key values (only for hash-type CBT’s) #

This works only for hash-type CBT’s.

If you do know that an X key is present in a CBT t, you can get that key’s value using t[X].

import strformat, typetraits, critbits
var t: CritBitTree[int]
t["a"] = 1
echo fmt"""t["a"] = {t["a"]}"""
t["a"] = 1

Trying to do t[X] for a set-type CBT will give this error:

nim_src_LF3H1K.nim(9, 9) template/generic instantiation from here
lib/pure/strformat.nim(313, 39) template/generic instantiation from here
lib/pure/collections/critbits.nim(203, 6) Error: undeclared field: 'val'

If you don’t know whether an X key is present in a CBT t, you need to first check if that key exists using hasKey.

import critbits
var t: CritBitTree[int]
t["a"] = 123
if t.hasKey("a"):
  echo """t["a"] = """, t["a"]
if t.hasKey("b"):
  echo """t["b"] = """, t["b"] # this will not be printed
t["a"] = 123

TODO Iterators #

Nim By Example #

Examples in this section are from https://nim-by-example.github.io/.

DONE Hello World #

See section Echo.

DONE Variables #

See section Variables.

DONE Result #

See section return keyword and result Variable.

DONE Type Casting and Inference #

Nim is a statically typed language. As such, each variable has a type associated with it.

Inferred Types #

As seen in the previous example these types are inferred in the const, let and var declarations by the compiler.

# These types are inferred.
var x = 5 # int
var y = "foo" # string

import typetraits
echo "x is ", x.type.name
echo "y is ", y.type.name
x is int
y is string

Assigning a value of a different type will result in a compile-time error.

var x = 5 # int
var y = "foo" # string
x = y
nim_src_PgW593.nim(7, 3) Error: type mismatch: got <string> but expected 'int'
Converting Types #

Type conversion can be done by calling the type as a proc.

import typetraits, strformat

var
  x = 1.0
  xType = x.type.name
echo fmt"x is {xType} and its value is {x}."

var
  xCasted = x.int # int(x) will also work
  xCastedType = xCasted.type.name
echo fmt"xCasted is {xCastedType} and its value is {xCasted}."
x is float64 and its value is 1.0.
xCasted is int and its value is 1.
Casting Types #

Casting is done using the cast keyword (which is not recommended). See section Specifying Types for an example.

Specifying Types #

You may optionally specify the type after a colon (:). In some cases the compiler will expect you to explicitly cast types, for which multiple ways are available:

  • type conversion, whose safety checked by the compiler

    import typetraits, strformat
    var x = int(1.0 / 3) # type conversion
    echo fmt"x is {x.type.name} and its value is {x}."
    x is int and its value is 0.
  • annotating the variable type. In the below example, y is an empty seq and it needs a type specification.

    import typetraits, strformat
    var y: seq[int] = @[] # empty seq needs type specification
    echo fmt"y is {y.type.name} and its value is {y}."
    y is seq[int] and its value is @[].

    Failing to specify the type for empty sequences will give an error like:

    nim_src_ofNFSG.nim(5, 9) Error: cannot infer the type of the sequence
  • the cast keyword, which is unsafe and should be used only where you know what you are doing, such as in interfacing with C

    var z = "Foobar"
    proc ffi(foo: ptr array[6, char]) =
      echo repr(foo)
    let
      zAddr = addr z[0]
      zAddrCastedToPtr = cast[ptr array[6, char]](zAddr)
    ffi(zAddrCastedToPtr)
    ref 0x7f6274536058 --> ['F', 'o', 'o', 'b', 'a', 'r']

DONE If, Else, While #

Nim has many different control flow constructs, including the standard if, else, and while. For “else if”, Nim uses elif.

  • When inside a loop, continue can be used to skip the rest of the loop body.
  • To begin the next iteration, break can be used to immediately leave the loop body.
import strutils, random

randomize()
let answer = rand(9) + 1 # random value in the range [1,10]
while true:
  echo "I have a number from 1 to 10, what is it? "
  let guess = parseInt(stdin.readLine)

  if guess < answer:
    echo "Too low, try again"
  elif guess > answer:
    echo "Too high, try again"
  else:
    echo "Correct!"
    break

Labeled block statement #

Along with its other uses, the block statement can be used to create a label so that it’s possible to break out of nested loops.

echo "I am outside"
block busyloops:
  echo "  I am in the busyloops block"
  while true:
    echo "    I am in the first while"
    while true:
      echo "      I am in the second while"
      break busyloops
    echo "    I am in the first while again"
  echo "  I am in the outside the first while"
echo "I am outside again"
I am outside
  I am in the busyloops block
    I am in the first while
      I am in the second while
I am outside again

If we wanted to use just break instead of break BLOCK_LABEL, we would have need to do this:

echo "I am outside"
block busyloops:
  echo "  I am in the busyloops block"
  while true:
    echo "    I am in the first while"
    while true:
      echo "      I am in the second while"
      break
    echo "    I am in the first while again"
    break
  echo "  I am in the outside the first while"
echo "I am outside again"
I am outside
  I am in the busyloops block
    I am in the first while
      I am in the second while
    I am in the first while again
  I am in the outside the first while
I am outside again

DONE Case Statements #

Nim also supports case statements, which are like switches in other languages.

  • You can use strings in the switch statement.

    proc abc(str: string) =
      case str
      of "alfa":
        echo "A"
      of "bravo":
        echo "B"
      of "charlie":
        echo "C"
      else:
        echo "Unrecognized letter"
    abc("alfa")
    abc("bravo")
    abc("charlie")
    abc("delta")
    A
    B
    C
    Unrecognized letter
  • Sets and ranges of ordinal types are also usable.

    proc vowel_consonant(c: char) =
      case c
      of 'a', 'e', 'i', 'o', 'u',
         'A', 'E', 'I', 'O', 'U':
        echo "Vowel"
      of 'b' .. 'd', 'f' .. 'h', 'j' .. 'n', 'p' .. 't', 'v' .. 'z',
         'B' .. 'D', 'F' .. 'H', 'J' .. 'N', 'P' .. 'T', 'V' .. 'Z':
        echo "Consonant"
      else:
        echo "Unknown"
    vowel_consonant('A')
    vowel_consonant('c')
    vowel_consonant('^')
    Vowel
    Consonant
    Unknown
  • case statements, like most things, are actually expressions.

    proc positiveOrNegative(num: int): string =
      result = case num
      of low(int) .. -1:
        "negative"
      of 0:
        "zero"
      of 1 .. high(int):
        "positive"
      else:
        "impossible"
    echo positiveOrNegative(-1)
    echo positiveOrNegative(10000000)
    echo positiveOrNegative(0)
    negative
    positive
    zero
  • It is required that the of statements cover every possible value for the case expression. If the of statements fail to do this, you need to include the else: condition as an umbrella to cover all those remaining cases.

    Compiling below will give an error:

    var b = true
    case b
    of true:
      echo "true"
    nim_src_cCE560.nim(5, 1) Error: not all cases are covered

    But the below works (of statements cover all cases):

    var b = true
    case b
    of true:
      echo "true"
    of false:
      echo "false"
    true

    And so does this (using else to cover remaining cases if any):

    var b = true
    case b
    of true:
      echo "true"
    else:
      echo "false"
    true

    Below is another example where the else is not needed, because the case statement covers all the possible values of f (just fooBar in this example).

    type
      Foo = enum
        fooBar
    let f: Foo = fooBar
    case f
      of fooBar:
        echo "fooBar"
    fooBar

Loose case syntax #

For historic reasons, the colon (:) at the end of the case statement is optional, and requirement to indent the following of statements is relaxed too.

So while all of the below styles compile, as per @dom96 from GitHub, the first style below is the “one-true-way” (ref).

  • No colon, no indented of

    case true
    of true:
      echo "yes"
    else:
      echo "no"
    yes
  • With colon, with indented of

    Personally I like this style as it is consistent with if, while, etc. But I will stick with the above officially preferred style everywhere for consistency.

    case true:
    of true:
      echo "yes"
    else:
      echo "no"
    yes
  • No colon, with indented of

    case false
      of true:
        echo "yes"
      else:
        echo "no"
    no
  • With colon, no indented of

    case false:
    of true:
      echo "yes"
    else:
      echo "no"
    no

DONE For Loops & Iterators #

Nim has first class iterators and syntax to use them — for loops. The continue and break keywords also work inside for loops.

items and pairs #

There are two kinds of iterator, and two special methods that for loops work with — items and pairs.

When iterating over an object with one item, Nim will call an iterator called items with the first parameter the type you want to iterate over.

The same thing happens when iterating with two items, but in that case, the pairs iterator is called.

type
  AlphaRange = object
    low: int
    high: int

iterator items(range: AlphaRange): int =
  var i = range.low
  while i <= range.high:
    yield i
    inc i

iterator pairs(range: AlphaRange): tuple[a: int, b: char] =
  for i in range:  # uses AlphaRange.items
    yield (i, char(i + ord('a') - 1))

for i, c in AlphaRange(low: 7, high: 11): # uses AlphaRange.pairs
  echo i, " ", c
7 g
8 h
9 i
10 j
11 k

Operators #

Iterators can also be operators in the standard way, with the name enclosed in backticks.

For example, the standard library defines the slice iterator .., which allows iterating through ordinal types. Below, the same iterator is mimicked using a different name ... (one more dot) to avoid conflict.

Always use a space before and after the iterator operators! See section Always use spaces before and after .. and ..<.

iterator `...`[T](a: T, b: T): T =
  var res: T = a
  # var res: T = T(a) # same as above, but a is explicitly type-casted with T.
  while res <= b:
    yield res
    inc res

for i in 0 ... 5:
  echo i
0
1
2
3
4
5

Just for fun, below an iterator with .++ operator is created that increments each output by 2.

iterator `.++`[T](a: T, b: T): T =
  ## Increment each output by 2
  var res: T = a
  while res <= b:
    yield res
    inc res
    inc res

for i in 0 .++ 8:
  echo i
0
2
4
6
8
Slice iterators .. and ..< #

The .. iterator includes the ending value in its output. So iterating through 0 .. 3 will iterate through 0 → 1 → 2 → 3. Think of range [a,b].

for i in 0 .. 3:
  echo i
0
1
2
3

The ..< iterator does not include that ending value. So iterating through 0 ..< 3 will iterate through 0 → 1 → 2. Think of range [a,b).

for i in 0 ..< 3:
  echo i
0
1
2

Inline Iterators #

Inline iterators basically take the body of the for loop and inline it into the iterator. This means that they do not have any overhead from function calling, but if carelessly created may increase code size dramatically.

iterator countTo(n: int): int =
  var i = 0
  while i <= n:
    yield i
    inc i

for i in countTo(5):
  echo i
0
1
2
3
4
5

Closure Iterators #

Closure iterators are procs that return iterators.

proc countTo(n: int): iterator(): int =
  ## returns an iterator with return type int
  return iterator(): int =
    var i = 0
    while i <= n:
      yield i
      inc i
  • Closure iterators hold on to their state and can be resumed at any time.
  • The finished() function can be used to check if there are any more elements available in the iterator.
  • However, raw iterator usage is unintuitive and difficult to get right.
proc countTo(n: int): iterator(): int =
  ## returns an iterator with return type int
  return iterator(): int =
    var i = 0
    while i <= n:
      yield i
      inc i
let countTo20 = countTo(20)
echo countTo20()
echo countTo20()

var output = ""
# Raw iterator usage:
while true:
  # 1. grab an element
  let next = countTo20()
  # 2. If the iterator doesn't have any more elements, break out of
  # this while loop.
  if finished(countTo20):
    break
  # 3. Loop body goes here:
  output.add($next & " ") # "$next" stringifies "next"

echo output
0
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Here the same countTo closure iterator is used to generate an iterator with a different max value.

proc countTo(n: int): iterator(): int =
  ## returns an iterator with return type int
  return iterator(): int =
    var i = 0
    while i <= n:
      yield i
      inc i
var output = ""
let countTo9 = countTo(9)
for i in countTo9():
  output.add($i)
echo output
0123456789

Counting up and down #

For counting up by increments of 1, you would normally use, what I call, the dot-dot iterator.

for i in 0 .. 3:
  echo i
0
1
2
3
countUp iterator #

The same thing can be done using the countUp iterator too:

for i in countUp(0, 3):
  echo i
0
1
2
3

This iterator, though, also allows setting the increment step (which defaults to 1):

for i in countUp(0, 3, 2):
  echo i
0
2
countDown iterator #

In the similar vein, there’s a countDown iterator for counting down too, which also has a decrement step parameter that defaults to 1.

for i in countDown(3, 0):
  echo i
echo ""
for i in countDown(3, 0, 2):
  echo i
3
2
1
0

3
1

DONE Procs #

Procedures in Nim are declared using proc and require their parameter and return types be annotated. Though, that type annotation is not needed if the parameters are assigned default values in the proc signature itself.

After the types and parameters, an = is used to denote the start of the function body.

Semicolons and type propagation #

Semicolons are used in a procedure’s signature to separate and group parameters of the same type together. This also provides visual distinction as to where a parameter type changes.

Below foo1 and foo2 are functionally the exact same – the int type propagates to b and then a, and the bool type propagates to d and then c.

# Using only commas
proc foo1(a, b: int, c, d: bool) =
  echo a, b, c, d
foo1(1, 2, true, false)

# Using semicolon for visual distinction
proc foo2(a, b: int; c, d: bool) =
  echo a, b, c, d
foo2(1, 2, true, false)
12truefalse
12truefalse

But incorrect use of semicolons will result in error. Below will leave the a parameter untyped as that “;” stopped the “int” type propagation.

proc foo3(a; b: int; c, d: bool) =
  echo a, b, c, d
foo3(1, 2, true, false)
nim_src_KzhwQV.nim(4, 11) Error: typeless parameters are obsolete

See Procedures for more.

UFCS / Command Invocation Syntax #

Nim procedures have Uniform Function Call Syntax (UFCS), which means that they can be called using either foo(a, b) or a.foo(b).

proc fibonacci(n: int): int =
  if n < 2:
    result = n
  else:
    result = fibonacci(n - 1) + (n - 2).fibonacci

See also Command Invocation Syntax.

Exporting Symbols #

Encapsulation (A language mechanism for restricting direct access to some of the object’s components.) is also supported, by annotating a procedure with *, which exports it and makes it available for use by modules.

If you have a module1.nim as below:

proc foo*(): int = 2
proc bar(): int = 3

And then if you have a module2.nim (in the same directory as module1.nim) as below:

import module1
echo foo()  # Valid
echo bar()  # will not compile

it will fail compilation with this error:

module2.nim(3, 6) Error: undeclared identifier: 'bar'

That’s because foo got exported from module1 because of foo*, but bar didn’t (because of the lack of *).

Side effect analysis #

Nim provides support for functional programming and so includes the {.noSideEffect.} pragma, which statically ensures there are no side effects.

Below will compile fine as it has no side effects:

proc sum(x, y: int): int {. noSideEffect .} =
  x + y

But below will fail:

proc minus(x, y: int): int {. noSideEffect .} =
  echo x
  x - y

with the error:

nim_src_8RNhaJ.nim(4, 6) Error: 'minus' can have side effects

Operators #

To create an operator, the symbols that are to be used must be encased inside backquotes (`) to signify that they are operators.

proc `$`(twoDArray: array[2, array[2, int]]): string =
  result = ""
  for arr_elem in twoDArray:
    for elem in arr_elem:
      result.add($elem & ", ")
    result.add("\n")
echo([[1, 2], [3, 4]])
1, 2,
3, 4,

See Varargs for how echo works in the above snippet.

And you even go crazy with operator names..

proc `^&*^@%`(a, b: string): string =
  ## A confusingly named useless operator
  result = a[0] & b[high(b)]
echo "foo" ^&*^@% "bar"
fr

Generic Functions (Generics) #

From Nim Tutorial 2:

Generics are Nim’s means to parametrize procs, iterators or types with type parameters. They are most useful for efficient type safe containers.

I think of generics as parameterized classes in SystemVerilog.

Below is a badly implemented example. While "a" * 10 works, 10 * "a" will fail compilation. But the example gives a good idea of what a generic function looks like.

proc `+`(a, b: string): string =
  a & b

proc `*`[T](a: T, b: int): T =
  result = ""
  for i in 0 .. b-1:
    result = result + a  # calls `+` proc for strings defined above

echo("a" * 10) # This works.
# echo(10 * "a") # But this won't.
aaaaaaaaaa

DONE First Class Functions #

From MDN web docs:

A programming language is said to have First-class functions when functions in that language are treated like any other variable. For example, in such a language, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable.

import sequtils # needed for filter
let powersOfTwo = @[1, 2, 4, 8, 16, 32]

proc greaterThan4(x: int): bool =
  x > 4
echo powersOfTwo.filter(greaterThan4)
@[8, 16, 32]

Closures #

From Closures:

Procedures can appear at the top level in a module as well as inside other scopes, in which case they are called nested procs.

A nested proc can access local variables from its enclosing scope and if it does so it becomes a closure. Any captured variables are stored in a hidden additional argument to the closure (its environment) and they are accessed by reference by both the closure and its enclosing scope (i.e. any modifications made to them are visible in both places). The closure environment may be allocated on the heap or on the stack if the compiler determines that this would be safe.

Two different syntaxes are available for closures:

  1. Anonymous procedures
  2. Do notation
Anonymous procedures #

These use the proc syntax identical to regular procedure syntax, but without the procedure name.

I think of anonymous procedures as the bare lambdas in Emacs-Lisp.

(cl-remove-if-not (lambda (x)
                    (> x 4))
                  '(1 2 4 8 16 32))
(8 16 32)

import sequtils # needed for filter
let powersOfTwo = @[1, 2, 4, 8, 16, 32]

# Using procname(arg1, arg2) syntax
echo filter(powersOfTwo, proc (x: int): bool = x > 4)
# Using Command Invocation Syntax: arg1.procname(arg2)
echo powersOfTwo.filter(proc (x: int): bool = x > 4)
Code Snippet 9: Anonymous Procedures
@[8, 16, 32]
@[8, 16, 32]
Do notation #
Ref
Do notation from manual

As a special more convenient notation, proc expressions involved in procedure calls can use the do keyword.

Below example is a re-written version of snippet 9 using the do notation:

import sequtils # needed for filter
let powersOfTwo = @[1, 2, 4, 8, 16, 32]

echo(powersOfTwo.filter do (x: int) -> bool: x > 4)
# Below does the same thing as above.
echo(powersOfTwo.filter do (x: int) -> bool:
  x > 4)
@[8, 16, 32]
@[8, 16, 32]

Below example is from Nim by Examples, but with a different value for cities. It sorts the city names from shortest names to the longest.

import algorithm # for sort
var cities = @["Frankfurt", "Ahmedabad", "Tokyo", "New York", "Kyiv", "Diu"]
sort(cities) do (x, y: string) -> int:
  cmp(x.len, y.len)
echo cities
@["Diu", "Kyiv", "Tokyo", "New York", "Frankfurt", "Ahmedabad"]

(See the documentation of sort in algorithm module for more info.)

While it works, I noticed that the sorting didn’t work like I wanted when the string lengths were the same.. I expected “Ahmedabad” to come before “Frankfurt”.

So here’s a slight tweak to the above sort algorithm to fix that:

import algorithm # for sort
var cities = @["Frankfurt", "Ahmedabad", "Tokyo", "New York", "Kyiv", "Diu"]
sort(cities) do (x, y: string) -> int:
  result = cmp(x.len, y.len)
  if result == 0 and x.len > 0:
    result = cmp(x[0], y[0])
echo cities
@["Diu", "Kyiv", "Tokyo", "New York", "Ahmedabad", "Frankfurt"]

Below example from the sort documentation sorts the names by surnames first. If the surnames match exactly, they are then sorted by names.

import algorithm # for sort
type Person = tuple[name: string, surname: string]
var people: seq[Person] = @[("Mike", "Smith"), ("Dave", "Smith"), ("Thomas", "Edison")]
people.sort do (x, y: Person) -> int:
  result = cmp(x.surname, y.surname)
  if result == 0:
    result = cmp(x.name, y.name)
echo people
@[(name: "Thomas", surname: "Edison"), (name: "Dave", surname: "Smith"), (name: "Mike", surname: "Smith")]
import sequtils # for map
var cities = @["Frankfurt", "Tokyo", "New York", "Kyiv"]
cities = cities.map do (x: string) -> string:
  "City of " & x
echo cities
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
Summary of anonymous procs and do notation #

Below example runs the same map proc using anonymous procs, and do notations, with and without the command invocation syntax.

import sequtils # for map
var cities1, cities2, cities3, cities4 = @["Frankfurt", "Tokyo", "New York", "Kyiv"]
# Anonymous procs
echo map(cities1, proc (x: string): string = "City of " & x)
echo cities2.map(proc (x: string): string = "City of " & x)
# Do notation
echo map(cities3) do (x: string) -> string: "City of " & x
echo cities4.map do (x: string) -> string: "City of " & x
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]
@["City of Frankfurt", "City of Tokyo", "City of New York", "City of Kyiv"]

do is written after the parentheses enclosing the regular proc params. The proc expression represented by the do block is appended to them.

In calls using the command syntax, the do block will bind to the immediately preceeding expression, transforming it in a call.

DONE Blocks #

Blocks can be introduced in two different ways:

  • by indenting statements
  • with ()s.

Blocks using indented statements #

The first way is to use indenting, e.g. using if-elif-else, while, for statements, or the block statement.

if true:
  echo "Nim is great!"

while false:
  echo "This line is never output!"

block:
  echo "This line, on the other hand, is always output"
Nim is great!
This line, on the other hand, is always output

The block statement can also be labeled, making it useful for breaking out of loops.

Block statements are useful for general scoping too.

import strformat, typetraits
let b = 3
echo fmt"Outside block: b is of type {b.type.name} of value {b}."
block:
  let b = "4"  # shadowing is probably a dumb idea
  echo fmt"  Inside block: b is of type {b.type.name} of value {b}."
echo fmt"Outside block: Once again, b is of type {b.type.name} of value {b}."
Outside block: b is of type int of value 3.
  Inside block: b is of type string of value 4.
Outside block: Once again, b is of type int of value 3.

Blocks using parentheses #

Parentheses can be used as an expression, but they do not provide the end of statement inference. So it is necessary to place semicolons yourself.

An interesting and unexpected side effect of this syntax is that Nim is suitable even for die-hard brace purists!

While possible, it doesn’t mean it’s a good idea. Most Nim code does not use parentheses in that way, and it would not be seen as idiomatic.

proc square(inSeq: seq[float]): seq[float] = (
  result = newSeq[float](len(inSeq));
  for i, v in inSeq: (
    result[i] = v*v;
  )
)
echo @[2.0, 3, 4].square
@[4.0, 9.0, 16.0]

TODO Primitive Types #

TODO Type Aliases #

TODO Object Types #

TODO Enum Types #

Ordinals #

TODO Distinct Types #

TODO Strings #

TODO Arrays #

TODO Seqs #

TODO Bitsets #

TODO Varargs #

TODO Object Oriented Programming #

TODO OOP Macro #

Nim in Action #

Threads #

6.2.1 The threads module and GC safety #

Problematic code #
var data = "Hello World"

proc showData() {.thread.} =
  echo(data)

var thread: Thread[void]
createThread[void](thread, showData)
joinThread(thread)
nim_src_I3YWGm.nim(6, 6) Error: 'showData' is not GC-safe as it
 accesses 'data' which is a global using GC'ed memory

Remove :eval no to see the above error.

Fixed code #
var data = "Hello World"

proc showData(param: string) {.thread.} =
  echo(param)

var thread: Thread[string]
createThread[string](thread, showData, data)
joinThread(thread)
Hello World

Parsing #

6.3.2 Parsing the Wikipedia page counts format #

Using regular expressions #
import re
let pattern = re"([^\s]+)\s([^\s]+)\s(\d+)\s(\d+)"
var line = "en Nim_(programming_language) 1 70231"
var matches: array[4, string]
let start = find(line, pattern, matches)

doAssert start      == 0
doAssert matches[0] == "en"
doAssert matches[1] == "Nim_(programming_language)"
doAssert matches[2] == "1"
doAssert matches[3] == "70231"

echo "Parsed successfully!"
Parsed successfully!
Parsing the data manually using split #
import strutils
var line = "en Nim_(programming_language) 1 70231"
var matches = line.split()

doAssert matches[0] == "en"
doAssert matches[1] == "Nim_(programming_language)"
doAssert matches[2] == "1"
doAssert matches[3] == "70231"

echo "Parsed successfully!"
Parsed successfully!
Parsing the data manually using parseutils #
import parseutils               # https://nim-lang.org/docs/parseutils.html
var line = "en Nim_(programming_language) 1 70231"

var i = 0
var domainCode = ""
#     parseUntil(s, token, until, start)
#       returns the number of parsed characters
i.inc parseUntil(line, domainCode, {' '}, i)
i.inc
var pageTitle = ""
i.inc parseUntil(line, pageTitle, {' '}, i)
i.inc
var countViews = 0
i.inc parseInt(line, countViews, i)
i.inc
var totalSize = 0
i.inc parseInt(line, totalSize, i)
i.inc

doAssert domainCode == "en"
doAssert pageTitle  == "Nim_(programming_language)"
doAssert countViews == 1
doAssert totalSize  == 70231

echo "Parsed successfully!"
Parsed successfully!

Unit testing #

Using plain assert in isMainModule #

type
  Card = object
    rank: Rank
    suit: Suit
  Rank = enum
    crSeven
    crEight
    crNine
    crTen
    crJack
    crQueen
    crKing
    crAce
  Suit = enum
    csClubs = "♧"
    csDiamonds = "♢"
    csHearts = "♡"
    csSpades = "♤"
proc `<`(a,b: Card): bool = a.rank < b.rank
when isMainModule:
  let
    aceDiamonds = Card(rank: crAce, suit: csDiamonds)
    kingClubs = Card(rank: crKing, suit: csClubs)
    aceClubs = Card(rank: crAce, suit: csClubs)

  assert aceDiamonds > kingClubs
  assert aceDiamonds == aceClubs

Evaluating above will throw this error:

Error: unhandled exception: aceDiamonds == aceClubs  [AssertionError]

Above < is implemented for Cards. The > operator is just sugar for backwards <.

isMainModule is used to do some quick tests. It means that if this module were to be imported and used in some application, that logic wouldn’t even be compiled into the binary, let alone evaluated.

Using the unittest module #

type
  Card = object
    rank: Rank
    suit: Suit
  Rank = enum
    crSeven
    crEight
    crNine
    crTen
    crJack
    crQueen
    crKing
    crAce
  Suit = enum
    csClubs = "♧"
    csDiamonds = "♢"
    csHearts = "♡"
    csSpades = "♤"
proc `<`(a,b: Card): bool = a.rank < b.rank
when isMainModule:
  import unittest
  suite "test card relations":

    setup:
      let
        aceDiamonds = Card(rank: crAce, suit: csDiamonds)
        kingClubs = Card(rank: crKing, suit: csClubs)
        aceClubs = Card(rank: crAce, suit: csClubs)

    test "greater than":
      check:
        aceDiamonds > kingClubs
        aceClubs > kingClubs
    test "equal to":
      check aceDiamonds == aceClubs

Breakdown of the unittest code #

We first define the test suite name.

suite "test card relations":

In that test suite, we do some initial setup, that defines the variables, etc. that will be used in the tests.

setup:
  let
    aceDiamonds = Card(rank: crAce, suit: csDiamonds)
    kingClubs = Card(rank: crKing, suit: csClubs)
    aceClubs = Card(rank: crAce, suit: csClubs)

Then we define tests using test "test name":, and use the check statements nested under those to do the checks, which look similar to the earlier assert approach.

# test 1
test "greater than":
  check:
    aceDiamonds > kingClubs
    aceClubs > kingClubs
# test 2
test "equal to":
  check aceDiamonds == aceClubs

Failure output #

unittest produces a more informative failure output as shown below.

TO BE FIXED [ob-nim or unittest] Above does not create the unittest error when evaluated using ob-nim #

But as you see in the Corrected code section, the unittest generated “pass” message gets output fine.

Issue filed at https://github.com/Lompik/ob-nim/issues/4.


For now the error message returned by unittest is pasted below manually, until I start getting it correctly on stderr via unittest.

[Suite] test card relations
  [OK] greater than
    cardsuite.nim(39, 24): Check failed: aceDiamonds == aceClubs
    aceDiamonds was (rank: crAce, suit: ♢)
    aceClubs was (rank: crAce, suit: ♧)
  [FAILED] equal to

Corrected code #

From the error, we see that only the “equal to” test fails. As the == proc is not defined for Card, Nim used the default == for object comparison, and so that check aceDiamonds == aceClubs test failed.

type
  Card = object
    rank: Rank
    suit: Suit
  Rank = enum
    crSeven
    crEight
    crNine
    crTen
    crJack
    crQueen
    crKing
    crAce
  Suit = enum
    csClubs = "♧"
    csDiamonds = "♢"
    csHearts = "♡"
    csSpades = "♤"
proc `<`(a,b: Card): bool = a.rank < b.rank
proc `==`(a,b: Card): bool = a.rank == b.rank
when isMainModule:
  import unittest
  suite "test card relations":

    setup:
      let
        aceDiamonds = Card(rank: crAce, suit: csDiamonds)
        kingClubs = Card(rank: crKing, suit: csClubs)
        aceClubs = Card(rank: crAce, suit: csClubs)

    test "greater than":
      check:
        aceDiamonds > kingClubs
        aceClubs > kingClubs
    test "equal to":
      check aceDiamonds == aceClubs
[Suite] test card relations
  [OK] greater than
  [OK] equal to

About DSL and unittest #

Nim is extremely effective at creating DSLs in ways that other languages simply don’t have at their disposal. Statements like check and suite in this unittest module almost act like additions to the syntax of the language itself; they’re not functions or classes, operating at runtime; they operate at compilation, accepting the tests written by the programmer as AST objects and manipulating them until the resulting code is quite different indeed.

Also see #

Reference #

Documentation #

TODO runnableExamples #

See itertools library as an example.

Miscellaneous #

Changing the nimcache/ directory #

Below tip by @zah from GitHub shows how to create the nimcache/ directory in /tmp by default.

You can place this in <nim folder>/config/nim.cfg or in ~/.config/nim.cfg:

@if unix:
  @if not release:
    nimcache = "/tmp/nimcache/d/$projectName"
  @else:
    nimcache = "/tmp/nimcache/r/$projectName"
  @end
@end

@if windows:
  @if not release:
    nimcache = r"C:\Temp\nimcache\d\$projectName"
  @else:
    nimcache = r"C:\Temp\nimcache\r\$projectName"
  @end
@end

NEED TO UNDERSTAND Pragmas #

noinit #

Prevents auto-initialization of local variables. So such variables would then hold random values.

proc a() =
  var ai {.noinit.}: array[5, int]
  echo ai
a()
[0, 4254186, 0, 140737207612416, 0]

See Uninitialized variables for details.

dirty #

I got curious why {.dirty.} pragma was added in this parsetoml commit:

template defineGetProcDefault(name: untyped,
                              t: typeDesc,
                              doccomment: untyped) {.dirty.} =
  proc name*(table: TomlTableRef,
             address: string,
             default: t): t =
..

and @PMunch from GitHub gave this nice explanation:

Normally a template is what you call hygienic. This means that none of the symbols in the template is visible outside of it, and will not clutter the namespace of the context it is called in or interfere with anything. Imagine calling a template twice which instantiates a temporary variable for example, this would fail if the template wasn’t hygienic as the variable would already exist when the template was called the second time.

In this case we only use the template to generate some procedures, and we want those to be injected into the main scope as is. The problem before was that the table, address, and default arguments would get changed to something like table243829, address231984, and default290432 in the generated code. This would mean that if you wanted to name your arguments you would have to use those names, not very pretty. Plus the documentation used those names and looked over-all a bit ugly. By marking the template as {.dirty.} we stop this behaviour and the argument names stay the same. Since all these templates does is create new procedures, which we are sure don’t collide with any name, we can do this safely.

NEED TO UNDERSTAND Questions/Doubts #

What are ptr and addr#

See the cast example in section Specifying Types where those get used.

Better understand the Nim “do notation” syntax #

See the example in First Class Functions. Does that notation always have to be on one line? Can it be broken across multiple lines for better readability?

See Do notation from manual.

References #


  1. If using Nim 0.18.0 or older, use nim --advanced. [return]
  2. The manual uses this pragma exchangeably as {.noInit.} and {.noinit.} at various places in documentation and Nim core code.. that was confusing. Actually that case of the inner I does not matter. I will just use {.noinit.} in this document for consistency. [return]