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, Nim by Example and many other places. This also includes my own musings and hard-learned lessons.
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 #

Nim version #

assert NimVersion is string
echo NimVersion
assert NimMajor is int
echo NimMajor
assert NimMinor is int
echo NimMinor
assert NimPatch is int
echo NimPatch
0.18.1
0
18
1

See Check Type (is) for is.

Echo with newlines #

The echo proc prints to the stdout with a newline added at the end.

echo("Hello World")
Hello World

The parentheses are optional.

echo "Hello World"
Hello World

Echo with multiple arguments #

From Nim Docs – echo:

proc echo(x: varargs[typed, `$`]) {..}

It means that echo accepts multiple arguments, and all of them are stringified using the $ proc.

echo outputs each of those stringified arguments on the stdout one after another and appends a newline at the very end. So the echo statements in the below snippet have the same outputs:

# First concatenating the strings and passing as single arg to echo
echo $100 & "abc" & $2.5
# Passing multiple args to echo, and letting echo "concatenate" them
echo 100, "abc", 2.5
100abc2.5
100abc2.5

writeLine #

Another way to print to the stdout is to use the writeLine proc, but by passing the stdout argument to the File parameter.

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

Echo without newlines #

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

Colored or Styled echo #

Use styledEcho template from the 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 #
type
  Style* = enum           ## different styles for text output
    styleBright = 1,      ## bright text
    styleDim,             ## dim text
    styleItalic,          ## italic (or reverse on terminals not supporting)
    styleUnderscore = 4,  ## underscored text
    styleBlink,           ## blinking/bold text
    styleReverse = 7,     ## reverse
    styleHidden           ## hidden text
    styleStrikethrough,   ## strikethrough
Foreground Color #
type
  ForegroundColor = enum
    fgBlack = 30,         ## black
    fgRed,                ## red
    fgGreen,              ## green
    fgYellow,             ## yellow
    fgBlue,               ## blue
    fgMagenta,            ## magenta
    fgCyan,               ## cyan
    fgWhite               ## white
Background Color #
type
  BackgroundColor = enum
    bgBlack = 40,         ## black
    bgRed,                ## red
    bgGreen,              ## green
    bgYellow,             ## yellow
    bgBlue,               ## blue
    bgMagenta,            ## magenta
    bgCyan,               ## cyan
    bgWhite               ## white
Other #
type
  TerminalCmd = enum
    resetStyle,           ## reset attributes
    fgColor,              ## set foreground's true color
    bgColor               ## set background's true color

styledEcho 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!^[[0m
unstyled 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”.

As you see above, styledEcho automatically adds the ANSI code for resetting the style (^[[0m) at the end of the line, before adding the trailing newline.

Echoing styled text without trailing newline #

Below snippet shows the use of stdout.styledWrite to echo strings of different styles (includes changing the foreground and background colors) on the same line.

import terminal
stdout.styledWrite(fgRed, "red text ")       # no newline here
stdout.styledWrite(fgGreen, "green text ")   # no newline here
stdout.styledWrite(fgBlue, "blue text ")     # no newline here
stdout.styledWrite(fgYellow, "yellow text ") # no newline here
stdout.styledWrite("ordinary text ")         # no newline here
stdout.styledWrite(fgCyan, "cyan text ")     # no newline here
echo ""
Code Snippet 1: Using stdout.styledWrite to echo styled string without trailing newline
^[[31mred text ^[[0m^[[32mgreen text ^[[0m^[[34mblue text ^[[0m^[[33myellow text ^[[0mordinary text ^[[36mcyan text ^[[0m

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 code snippet is similar to the snippet in 1, except that the low-level procs setForeGroundColor, resetAttributes and stdout.write are used instead of stdout.styledWrite.

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#

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 #

You can pass a block of statements as a last argument to a template call following the special : as shown below:

template foo(body: untyped) =
  body
foo():  # special colon
  echo "hello"
  echo "world"
hello
world

The untyped is optional, but using it as in the above example is recommended.

template foo(body) =
  body
foo():  # special colon
  1

DONE Return type of templates #

Below template has a return type of “void” and it still is returning a literal integer 1. So you get an error.

template foo() =
  1
echo foo()
nim_src_co9l9c.nim(6, 9) Error: expression '1' is of type 'int literal(1)' and has to be discarded

To fix that, you need to assign the correct return type for the foo template:

template foo(): int =
  1
echo foo()
1

Or set it to untyped so that the template replacement is done before any semantic checking or type resolution.

template foo(): untyped =
  1
echo foo()
1

Thanks to @mratsim from GitHub for this reply to my question regarding untyped return type for templates:

untyped is useful to make sure the replacement is done before any semantic checking/type resolution.

If you are getting confused about whether or not to set a template’s return type or what to set it to, just set it to untyped.

If in doubt, set a template’s return type as untyped.

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).

TODO Quote Helper #

Nim Docs – Macros / quote

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 #

DONE int types #

int8
8-bit signed integer type
int16
16-bit signed integer type
int32
32-bit signed integer type
int64
64-bit signed integer type
int
This is the same size as the size of the pointer. So if the code is compiled in 32-bit mode, int will be the same size as int32, and if it’s compiled in 64-bit mode (on a 64-bit CPU), it will be the same size as int64.

Below code is compiled in 32-bit mode:

import strformat
echo fmt"Size of int32 / int / int64 = {sizeof(int32)} / *{sizeof(int)}* / {sizeof(int64)}"
Size of int32 / int / int64 = 4 / *4* / 8

And below is compiled in the usual 64-bit mode – Notice the change in the size of int type:

import strformat
echo fmt"Size of int32 / int / int64 = {sizeof(int32)} / *{sizeof(int)}* / {sizeof(int64)}"
Size of int32 / int / int64 = 4 / *8* / 8

Below is also compiled using the default 64-bit mode.

import strformat, typetraits
var
  aInt: int = 1
  aInt8: int8 = 2
  aInt16: int16 = 3
  aInt32: int32 = 4
  aInt64: int64 = 5
echo fmt"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt64 = aInt8 # works
aInt64 = aInt16 # works
aInt64 = aInt32 # works
aInt64 = aInt # works
echo fmt"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt = aInt8 # works
aInt = aInt16 # works
aInt = aInt32 # works
# aInt = aInt64 # Error: type mismatch: got <int64> but expected 'int'
echo fmt"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt32 = aInt8 # works
aInt32 = aInt16 # works
# aInt32 = aInt # Error: type mismatch: got <int> but expected 'int32'
# aInt32 = aInt64 # Error: type mismatch: got <int64> but expected 'int32'
echo fmt"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt16 = aInt8 # works
# aInt16 = aInt32 # Error: type mismatch: got <int32> but expected 'int16'
# aInt16 = aInt # Error: type mismatch: got <int> but expected 'int16'
# aInt16 = aInt64 # Error: type mismatch: got <int64> but expected 'int16'
echo fmt"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
# aInt8 = aInt16 # Error: type mismatch: got <int16> but expected 'int8'
# aInt8 = aInt32 # Error: type mismatch: got <int32> but expected 'int8'
# aInt8 = aInt # Error: type mismatch: got <int> but expected 'int8'
# aInt8 = aInt64 # Error: type mismatch: got <int64> but expected 'int8'
aInt64 = 5, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 1
aInt64 = 1, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 1
aInt64 = 1, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 4
aInt64 = 1, aInt32 = 3, aInt16 = 3, aInt8 = 2, aInt = 4
aInt64 = 1, aInt32 = 3, aInt16 = 2, aInt8 = 2, aInt = 4

For 64-bit compilation, while both int and int64 types are 64-bit integer types, they are of different “levels”. “Levels” is my home-grown term as I don’t know how to explain this better based on what I am seeing.

Here’s my attempt at explaining that:

    int64 > int > int32 > int16 > int8

My home-grown theory follows:

  • int64 is at the “highest level”, and int8 is at the “lowest level”.
  • You can assign a “lower level” typed variable to a “higher level” typed variable, but not the other way around.

So that’s why aInt64 = aInt works, but aInt = aInt64 fails.

Similarly aInt = aInt32 works, but aInt32 = aInt fails — That exact same would apply to the 32-bit compilation too.

Related:

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

String Stuff #

See Strings for an introduction to the string datatype in Nim.

String Functions #

% and format #

The strutils module defines % and format for string formatting (along with many other things!).

% needs the second argument to be a string, or an array or sequence of strings.

import strutils
echo "$1 $2" % ["a", "b"]
echo "$1 $2" % @["a", "b"]
# echo "$1 $2" % [100, 200] # This gives error. % cannot have int list as arg, has to be an array/seq of strings
# echo "$1 $2" % ['a', 'b'] # This gives error. % cannot have char list as arg, has to be an array/seq of strings
echo "$1 $2" % [$100, $200]
a b
a b
100 200

format does not have the requirement for the input to be a string (or an array/seq of strings) – It auto-stringifies the elements in the second argument.

import strutils
echo "$1 $2".format(["a", "b"])
echo "$1 $2".format("a", "b")
echo "$1 $2".format('a', 'b') # format, unlike % does auto-stringification of the input
echo "$1 $2".format(100, 200) # format, unlike % does auto-stringification of the input
a b
a b
a b
100 200

strformat and fmt #

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.

import strformat
let
  a = 100
  b = "abc"
echo fmt"a = {a}, b = {b}"
a = 100, b = abc
TO BE FIXED 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

I understand that this is a limitation of the nature of implementation of fmt because it needs its formatting string available at compile time. But I wish this limitation wasn’t there.

String functions: Nim vs Python #

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.
    • The single quote character literal is represented by '\39'.

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', '\39'
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 #

Use &.

let s = "abc" & "def"
echo s
echo "ghi" & "jkl"
abcdef
ghijkl

String Comparison #

== and != can be used to compare if two strings are equal.

assert "abc" == "abc"
assert "abc" != "acb"

The above is obvious. But see below for the “less than (or equal to)” and “greater than (or equal to)” comparisons:

  • If strings X and Y are of equal lengths, walking through the characters of both strings from the left-most side, for the first set of non-equal characters char-in-X and char-in-Y, char-in-X < char-in-Y => X < Y.

    assert "a" < "b"
    assert "bb" > "ba"
    assert "ab" < "ba"
    assert "abc" < "abd"
    assert "bbc" > "abc"
  • The above rule applies even if strings X and Y are not of equal lengths.

    So even if string X has more characters than string Y, X would be less than Y if for the first set of non-equal characters char-in-X and char-in-Y, char-in-X < char-in-Y.

    assert "a" < "ab"
    assert "ab" > "a"
    assert "ab" < "b"

So string comparison is not a good way for version comparison:

echo NimVersion
assert NimVersion >= "0.18.0"
assert NimVersion < "00.18.0" # Surprise!
assert NimVersion >= "0.09.0"
assert NimVersion < "0.9.0" # Surprise!
0.18.1

See Tuple comparison instead for Nim version comparison.

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 2: 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 3: 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 2, but with the difference that you can control the randomization seed.

import random
randomize(123)
echo rand(high(int))
Code Snippet 4: 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.

DONE rand does not accept int64 input #

Below gives an error.

import random, strformat
echo fmt"rand(9223372036854775807) = {rand(9223372036854775807)}"
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)

That’s because the rand proc doesn’t accept/return int64 type value.

But it does accept int type. Even though both int and int64 might be 64-bit integer types, they are not technically the same type!

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

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 5: 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 6: 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

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 7: 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 8: 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 9: 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 #

Check Type (is#

Use the is proc like an operator. FOO is TYPE returns true if FOO is of type TYPE.

echo "'c' is char? ", 'c' is char
echo "'c' is string? ", 'c' is string
echo """"c" is string? """, "c" is string
'c' is char? true
'c' is string? false
"c" is string? true

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.

Arrays with enum as length #

Array types can be declared as array[<some_enum_type>, <element_type>] too. In this case, the array length will be set equal to the number of values in that enum type, and so it has to be assigned exactly that many elements.

type
  Foo = enum
    oneHundred,
    twoHundred,
    threeHundred
proc dict(f: Foo) =
  const
    fooInt: array[Foo, int] = [100, 200, 300]
    fooString: array[Foo, string] = ["one hundred", "two hundred", "three hundred"]
  echo fooInt[f], " ", fooString[f]
dict(twoHundred)
200 two hundred

As seen from the above example, such “enum-length arrays” can serve as “dictionaries” to provide a one-to-one translation from each enum value to something else.

If such “enum-length arrays” are not assigned the required number of elements (equal to the number of enum values), you get a compile error.

type
  Foo = enum
    oneHundred,
    twoHundred,
    threeHundred
proc dict(f: Foo) =
  const
    fooInt: array[Foo, int] = [100, 200]
  echo fooInt[f]
nim_src_KdnP9k.nim(11, 31) Error: type mismatch: got <array[0..1, int]> but expected 'array[Foo, int]'

Here’s another example from Nim By Example – Arrays:

type
  PartsOfSpeech = enum
    speechPronoun, speechVerb, speechArticle,
    speechAdjective, speechNoun, speechAdverb
let partOfSpeechExamples: array[PartsOfSpeech, string] = [
  "he", "reads", "the", "green", "book", "slowly"
]
echo partOfSpeechExamples
["he", "reads", "the", "green", "book", "slowly"]

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 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).

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.

Tuple comparison #

let NimVersionT = (major: NimMajor, minor: NimMinor, patch: NimPatch)
echo NimVersionT
assert (0, 18, 0) < NimVersionT
assert (0, 18, 1) == NimVersionT
assert (0, 18, 2) > NimVersionT
assert (0, 19, 0) > NimVersionT
assert (1, 0, 0) > NimVersionT
(major: 0, minor: 18, patch: 1)

TODO Objects #

TODO Pointers and References #

TODO Pointer (ptr#

Dereferencing #

import strformat, typetraits
type Foo = ref object
  x, y: float

var f: Foo
new f

# Accessing the reference
echo fmt"Type of f = {$type(f)}"
echo fmt"{$type(f[])} f[] = {f[]}"

f[].y = 12
echo fmt"{$type(f[])} f[] = {f[]}"

# When accessing values the dereference operator [] can be left out.
f.x = 13.5
echo fmt"{$type(f[])} f[] = {f[]}"
Type of f = Foo
Foo:ObjectType f[] = (x: 0.0, y: 0.0)
Foo:ObjectType f[] = (x: 0.0, y: 12.0)
Foo:ObjectType f[] = (x: 13.5, y: 12.0)

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.

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? false
  creating /tmp/foo/bar ..
/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

Importing and Exporting Modules #

import #

The import statement can contain just the module name with the path, if needed too.

  • Importing a module present in the current directory.

    foo/
    ​  - foo.nim
    ​  - bar.nim
    # Inside foo.nim
    import bar
  • Importing a module present in a sub-directory.

    foo/
    ​  - foo.nim
      extra/
    ​    - bar.nim
    # Inside foo.nim
    import extra/bar

export #

The export statement can contain only the module name – No paths (ref).

  • Importing and then exporting a module present in the current directory.

    foo/
    ​  - foo.nim
    ​  - bar.nim
    # Inside foo.nim
    import bar
    export bar
  • Importing a module present in a sub-directory, and then exporting the same.

    foo/
    ​  - foo.nim
      extra/
    ​    - bar.nim
    # Inside foo.nim
    import extra/bar
    export bar

Exporting identifiers (Asterisks after proc names, etc.) #

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] =

See Object Types for exporting objects and their individual fields.

Modules/Packages/Libraries #

sugar (future on Nim 0.18.0 and older) #

This is a standard Nim library.

This module implements nice syntactic sugar based on Nim’s macro system.

DONE Lambdas (=>#

Here is an example of using lambda in Emacs Lisp, that returns the 1-incremented value of the input.

(let ((plus1 (lambda (x)
                (+ x 1))))
  (funcall plus1 2))
Code Snippet 10: Example of lambda in Emacs Lisp

Here’s the same using the => macro in Nim:

import sugar
proc int2Int(fn: proc(inp: int): int, x: int): int = # Define the proc parameter and return types
  ## Wrapper proc to define the ``fn`` type, and to pass its inputs.
  fn(x) # Call the passed fn with input x
echo int2Int(x => x + 1, 2) # Call the int2Int wrapper with the fn definition and input arg
Code Snippet 11: Example of lambda-like => macro
3
  • The int2Int wrapper as in the above example is needed because Nim needs to know the types of the fn proc parameters and its return value.
  • Later we call that wrapper with the fn definition x => x + 1 and its input argument 2.

Thanks to @narimiran from GitHub for this snippet as an another example of using =>, with map.

import sequtils, sugar

let
  a = [1, 2, 3, 5, 7]
  b = a.map(x => 2*x)

echo b
@[2, 4, 6, 10, 14]

Also see Anonymous procedures.

DONE Syntactic sugar for proc types (->#

The -> macro helps abbreviate proc types.

  • proc(inp: int): int can be replaced with int -> int.
  • proc(x: int; y: float): string can be replaced with (int, float) -> string.

So the snippet in 11 can be rewritten using -> as shown below:

import sugar
proc int2Int(fn: int -> int, x: int): int =
  ## Wrapper proc to define the ``fn`` type, and to pass its inputs.
  fn(x)
echo int2Int(x => x + 1, 2)
Code Snippet 12: Example of using proc type sugar: ->
3

Here are few more examples of using => and ->:

import sugar

proc twoInts2Int(fn: (int, int) -> int; x, y: int): int =
  ## Wrapper proc to define the ``fn`` type, and to pass its inputs.
  fn(x, y)

proc twoFlts2Flt(fn: (float, float) -> float; x, y: float): float =
  ## Wrapper proc to define the ``fn`` type, and to pass its inputs.
  fn(x, y)

echo twoInts2Int((x, y) => x + y, 2, 3)
echo twoFlts2Flt((x, y) => x + y, 1.11, 2.22)
5
3.33

The above example can be made more concise by using Generics. Thanks to @narimiran from GitHub for pointing out that the known types for the inputs need to come first in the twoInpOneOut proc signature below.

As the type of fn is unknown, it cannot be placed as the first parameter of twoInpOneOut as that would fail the auto-inference of types of x and y.

import sugar

proc twoInpOneOut[T](x, y: T; fn: (T, T) -> T): T =
  ## Wrapper proc to define the ``fn`` type, and to pass its inputs.
  fn(x, y)

echo twoInpOneOut(2, 3, (x, y) => x + y)
echo twoInpOneOut(1.11, 2.22, (x, y) => x + y)
echo twoInpOneOut("abc", "def", (x, y) => x & y)
5
3.33
abcdef

DONE List Comprehension ([] and lc#

List comprehension is implemented in this module using the [] macro.

The syntax is:

lc[<RETURN_EXPR using ELEMs> | (<ELEM1> <- <LIST1> [, <ELEM2> <- <LIST2>, ..] [, <COND>]), <SEQ_TYPE>]
  • The whole lc[ .. ] expression returns a sequence of type seq[SEQ_TYPE].
  • ELEM1 is an element of LIST1, ELEM2 is an element of LIST2, and so on.
    • <ELEM2> <- <LIST2> and onwards are optional.
  • LIST2 can use earlier declared element vars like ELEM1.
    • LIST3 can use earlier declared element vars like ELEM1 and ELEM2, and so on.
  • Optional COND can use one or more of the declared element vars.
  • RETURN_EXPR can use one or more of the declared element vars.
    • RETURN_EXPR is of the type SEQ_TYPE.

Here’s an example (Ref) that splits a string into a sequence of characters:

import strformat, typetraits, sugar
let
  str = "nim lang"
  cSeq = lc[c | (c <- str),  # lc[EXPR | (ELEM1 <- LIST1),
            char]            #    SEQ_TYPE]
echo fmt"str of type {$str.type} = {str}"
echo fmt"cSeq of type {$cSeq.type} (seq[SEQ_TYPE]) = {cSeq}"
str of type string = nim lang
cSeq of type seq[char] (seq[SEQ_TYPE]) = @['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']

Though, the above example can be written more concisely using map and =>:

import sequtils, sugar
let
  str = "nim lang"
  cSeq = str.map(c => c)
echo cSeq
@['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']

Other list comprehension examples:

import sugar

let
  numList = 1 .. 10
  evenNums = lc[x | (x <- numList,  # lc[EXPR | (ELEM1 <- LIST1,
                     x mod 2 == 0), #            COND),
                int]                #    SEQ_TYPE]
echo evenNums
@[2, 4, 6, 8, 10]

The LIST2 and onwards can also use one or more of the prior declared ELEMs.

import sugar

const
  n = 15
let
  numList = 1 .. n
  rightAngleTriangleSides =
    lc[(x, y, z) | (x <- numList,      # lc[EXPR | (ELEM1 <- LIST1,
                    y <- x .. n,       #            ELEM2 <- LIST2,
                    z <- y .. n,       #            ELEM3 <- LIST3,
                    x*x + y*y == z*z), #            COND),
       tuple[a, b, c: int]]            #    SEQ_TYPE]
echo rightAngleTriangleSides
@[(a: 3, b: 4, c: 5), (a: 5, b: 12, c: 13), (a: 6, b: 8, c: 10), (a: 9, b: 12, c: 15)]

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

strfmt (Yep, this is different from strformat#

Thanks to the tip by @Yardanico from GitHub, the strfmt module by lyro allows using a string formatting syntax that’s similar to Python’s Format Specification Mini-Language (.format()).

You will need to install it first by doing nimble install strfmt.

Unfortunately though, the strfmt developer has stopped using Nim. Also the fmt identifier from this package clashes with the fmt in the strformat module that got added to Nim 0.18.0, if both strfmt and strformat modules are imported in a Nim project. So from the aspect of future support, it might be better to use just the strformat module. 😞

Example of .format use in Python 3.7.0:

print('{} {}'.format(1, 2))
print('{} {}'.format('a', 'b'))
1 2
a b

Similar example using fmt from strfmt module:

import strfmt
echo "{} {}".fmt(1, 0)
echo "{} {}".fmt('a', 'b')
echo "{} {}".fmt("abc", "def")
echo "{0} {1} {0}".fmt(1, 0)
echo "{0.x} {0.y}".fmt((x: 1, y:"foo"))
1 0
a b
abc def
1 0 1
1 foo

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 defines 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 #

TODO json #

Looping through keys in JSON #

Ref

import json

var js = parseJson"""
  [{"mango":"green"}
   , {"orange":"yellow"}
   , {"peach":"red"}
   , {"grape":"black"}]"""

var keys = newSeq[string]()
for obj in js:
  for k, _ in obj:
    keys.add k

echo keys
@["mango", "orange", "peach", "grape"]

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
Code Snippet 13: countTo closure iterator defined with explicit return type
  • 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
Closure Iterator: auto return type #

As seen in 13, the return type of that closure iterator is iterator(): int. But that return type is already defined in that closure in the return iterator(): int = line.

The Nim auto type can be used to help to prevent that repetition. Here’s the same snippet rewritten using auto return type:

import strformat, typetraits
proc countTo(n: int): auto =
  ## Returns an iterator with return type int
  return iterator(): int =
    var i = 0
    while i <= n:
      yield i
      inc i

echo fmt"countTo(10) is of type `{$countTo(10).type}'"
Code Snippet 14: countTo closure iterator defined with auto return type
countTo(10) is of type `iterator (): int{.closure, noSideEffect, gcsafe, locks: 0.}'

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.

Some terminology — For a proc with a signature proc foo(a: int) = {..}, and its call foo(100), a is called a parameter, and 100 is an argument to foo. (Ref: Nim Tutorial – Procedures)

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 15: Anonymous Procedures
@[8, 16, 32]
@[8, 16, 32]

Also see Lambdas (=>).

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 15 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]

DONE Primitive Types #

Nim has several primitive types:

signed integers
int8, int16, int32, int64, and int, where int is the same size as a pointer (see int types for more)
unsigned integers
similar, but unsigned with “u” prepended to the type
floating point numbers
float32, float64, and float (Note that float and float64 are the same, they both refer to the internal Float type.)
characters/bytes
char and byte, which are basically an alias for uint8

To indicate the size of an integer literal, append “u” or “i” and the size you’d like to the end. Example: 5'u8. However, usually this is not necessary.. unless you are dealing with two’s complement literals.

Integers can also have 0[xX], 0o, 0[bB] prepended to indicate a hex, octal, or binary literal, respectively. Examples: 0xABCD, 0o567, 0b1010.

Underscores are also valid in literals, and can help with readability.

import strformat, typetraits
let
  a: int8 = 0x7F
  b: uint8 = 0b1111_1111
  c = 0xFF # type is int
  d = 0o67 # value is written as an octal, type is int
  e: byte = 62
  # f: uint8 = 256 # Compile time error
echo fmt"Value of {a.type.name} a is {a}"
echo fmt"Value of {b.type.name} b is {b}"
echo fmt"Value of {c.type.name} c is {c}"
echo fmt"Value of {d.type.name} d is {d}"
echo fmt"Value of {e.type.name} e is {e}"
Value of int8 a is 127
Value of uint8 b is 255
Value of int c is 255
Value of int d is 55
Value of byte e is 62

Signed binary representation / Two’s complement #

What is a two’s complement?

Below will give compilation error:

let minusOne: int8 = 0b1111_1111
echo minusOne
Code Snippet 16: Wrong way of assigning a two's complement
nim_src_Kpkxfl.nim(4, 22) Error: type mismatch: got <int literal(255)> but expected 'int8'

Don’t do that! Instead, do:

let
  minusOne = 0b1111_1111'i8
echo minusOne
Code Snippet 17: Correct way of assigning a two's complement
-1

Explicitly write two’s complement notations with the intended type, like 0b1010_1010'i8 or 0xFFFF'i16.

In code snippet 16, Nim simply converts binary “1111_1111” to 255 and throws the above error as that obviously cannot be stored as an 8-bit signed integer.

Whereas in code snippet 17, Nim directly stores binary “1111_1111” as signed 8-bit integer and auto-recognizes that as a two’s complement and evaluates that as decimal “-1”.

Integer Operators #

Instead of ^, &, |, >>, << in most other languages, the xor, and, or, shr, shl operators are used, respectively.

import strformat, typetraits
let
  a: int = 0b1011_1010
  b: int = 0b0101_0011
echo fmt"{a:#010b} ^  {b:#010b} = {(a xor b):#010b}"
echo fmt"{a:#010b} &  {b:#010b} = {(a and b):#010b}"
echo fmt"{a:#010b} |  {b:#010b} = {(a or b):#010b}"
echo fmt"{a:#010b} >> 3          = {(a shr 3):#b}"
echo fmt"{a:#010b} << 5          = {(a shl 5):#b}"
0b10111010 ^  0b01010011 = 0b11101001
0b10111010 &  0b01010011 = 0b00010010
0b10111010 |  0b01010011 = 0b11111011
0b10111010 >> 3          = 0b10111
0b10111010 << 5          = 0b1011101000000

DONE Type Aliases #

Types are declared inside type sections, where multiple types can be declared.

Note that aliased types are the same as the original types, and so you can have expressions with a mix of original and aliased types.

If type safety is desired, distinct types should be used.

type
  MyInteger = int

let a: int = 2
echo a + MyInteger(4)
6

DONE Object Types #

In Nim, objects are like structs from C family languages and define a grouping of fields. They are by default traced by the garbage collector, so there is no need to explicitly free them when allocated.

type
  Animal = object
    name, species: string
    age: int

proc sleep(a: var Animal) =
  a.age += 1

proc dead(a: Animal): bool =
  result = a.age > 20

var carl = Animal(name : "Carl",
                  species : "L. glama",
                  age : 12)

let joe = Animal(name : "Joe",
                 species : "H. sapiens",
                 age : 23)

assert(not carl.dead)
for i in 0 .. 10:
  carl.sleep()
assert carl.dead

Object types are declared in a type section, as usual. They can be exported, and individual fields can also be exported.

type
  Animal* = object            # Export Animal object type
    name*, species*: string   # Export only name and species fields
    age: int

Fields can be safely exported without violating encapsulation because call syntax is equivalent between them.

Initially, carl is created on the stack and initialized to zeros, so its value is [name = nil, species = nil, age = 0]. It is mutable (because declared using var), so that means that the contents of carl can be changed. This also means it can be passed to functions that require a variable parameter, like sleep(), which can modify its value.

joe is also created on the stack, but its contents are immutable (because declared using let) and can not be changed. Attempting to do so, say through joe.age = 57, will fail with an error at compile time.

Object and References #

type
  Animal = object
    name, species: string
    age: int

proc sleep(a: var Animal) =
  a.age += 1

let mittens: ref Animal = new(Animal)
# Everything initialized to 0 or nil
echo mittens[]

# Initialize all the fields
mittens[].name = "Mittens"
# When accessing fields of refs, the dereferencing operator [] is
# optional.
mittens.species = "P. leo"
mittens.age = 6

echo mittens[]
mittens[].sleep()
echo mittens[]
(name: nil, species: nil, age: 0)
(name: "Mittens", species: "P. leo", age: 6)
(name: "Mittens", species: "P. leo", age: 7)

mittens is a reference to an object allocated on the heap (See Heap and Stack). So the value of mittens cannot be changed.

While mittens can never point to anything else, the value that mittens is pointing at can be changed as shown above. The fields in the Animal object referenced by mittens are changed from the default initialization value of zeros.

As the referenced object (mittens[]) is mutable, that can be passed to functions like sleep that require a variable parameter.

Below shows a more concise way of initializing reference types — by creating a type alias AnimalRef for ref Animal:

type
  Animal = object
    name, species: string
    age: int
  AnimalRef = ref Animal

proc sleep(a: var Animal) =
  a.age += 1

var spot = AnimalRef(name: "Spot",
                     species: "C. lupus",
                     age: 1)
echo spot[]
spot[].sleep()
echo spot[]
(name: "Spot", species: "C. lupus", age: 1)
(name: "Spot", species: "C. lupus", age: 2)

In many cases it is only wanted to have the object be a reference type to begin with, which is possible by declaring it as a ref object.

type
  Thing = ref object
    positionX, positionY: int

new keyword #

Try not to use the new keyword to initialize objects.

From this comment by Araq from Nim Forum:

Always prefer the syntax student = Student(name: "Anton", age: 5, id: 2) over new.

new makes it much harder for the compiler to prove a complete initialization is done. The optimizer will also soon take advantage of this fact.

More #

  • When to use ‘ref object’ vs plain ‘object’
  • Tuples vs Objects vs Ref Objects?

    Thanks to yglukhov from Nim Forum for this reply:

    The main conceptual difference between smth and ref smth is sharing. You can have several locations pointing to ref smth and if you change smth from one location the change will be visible through another location. That’s not the case with non - ref smth. So ref object is not the default – you have to consciously chose it depending on your design. Also inheritable/polymorphic hierarchies are mostly useful when they are ref object.

    Size of the objects (and whether it fits on stack) almost never has to be considered in reality.

    Under the hood, ref smth implies heap allocation and an extra access indirection. Heap allocation is more expensive than stack allocation, so it might matter in performance critical code paths. But then again, refs are generally faster to assign to each other, as they take up a single machine word.

    Tuples are almost like objects. Tuple fields are always public. Tuples can be anonymous, and they can’t be inherited. Generally they are used to represent some short living values that don’t deserve their own named type.

DONE Enum Types #

Enums in Nim are type-checked.

type
  CompassDirections = enum
    cdNorth, cdEast, cdSouth, cdWest

  Signals = enum
    sigQuit = 3, sigAbort = 6, sigKill = 9 # enum with holes

  Colors {.pure.} = enum
    Red = "FF0000", Green = (1, "00FF00"), Blue = "0000FF"

var
  dir: CompassDirections = cdNorth
  sig: Signals = sigQuit
  colRed = Colors.Red # qualified enum value reference
  colGreen = Colors.Green # qualified enum value reference

echo dir
echo sig
echo colRed
echo colGreen
Code Snippet 18: Enum Types (normal, enums with holes, enums with stringified values)
cdNorth
sigQuit
FF0000
00FF00

Notice that each element in CompassDirections is prepended with cd to avoid name conflicts since references to the enum value do not need to be qualified.

Enums can be given custom values as shown by Signals, or even given stringified values, as shown by Colors. Enums with non-incrementing and non-continuous values like in this Signals example are called “enums with holes”.

The {.pure.} pragma that Colors has, requires that all references to Colors’s values be qualified, therefore making a prefix unnecessary. (Note a bug on Nim devel with {.pure.} enums – Nim Issue #8066).

Ordinals #

Ordinals have low, high, pred, succ, dec, inc, and ord methods defined, where:

low
gives the lowest possible value
high
gives the highest possible value
pred
gives the ordinal value that’s n “steps” away from the current value in decreasing order (predecessor)
succ
gives the ordinal value that’s n “steps” away from the current value in increasing order (successor)
dec
decrements the ordinal by n “steps”
inc
increments the ordinal by n “steps”
ord
gives the integer value of the ordinal
import strformat, typetraits
var
  someOrdinal = 'A'                 # char is an ordinal
echo fmt"someOrdinal ({$type(someOrdinal)}) = {someOrdinal}"

echo fmt"low value of someOrdinal: {repr(someOrdinal.low)}"
echo fmt"high value of someOrdinal: {repr(someOrdinal.high)}"

echo fmt"value of someOrdinal 1 step away in decrementing order: {someOrdinal.pred} (curr value: {someOrdinal})"
echo fmt"value of someOrdinal 3 steps away in decrementing order: {someOrdinal.pred(3)} (curr value: {someOrdinal})"

echo fmt"value of someOrdinal 1 step away in incrementing order: {someOrdinal.succ} (curr value: {someOrdinal})"
echo fmt"value of someOrdinal 3 steps away in incrementing order: {someOrdinal.succ(3)} (curr value: {someOrdinal})"

inc someOrdinal
echo fmt"someOrdinal after incrementing once: {someOrdinal}"
someOrdinal.inc(2)
echo fmt"someOrdinal after incrementing twice: {someOrdinal}"

someOrdinal.dec
echo fmt"someOrdinal after decrementing once: {someOrdinal}"
dec(someOrdinal, 4)
echo fmt"someOrdinal after decrementing 4 times: {someOrdinal}"

echo fmt"value of someOrdinal: {someOrdinal}"
echo fmt"integer value of someOrdinal: {ord(someOrdinal)}"
someOrdinal (char) = A
low value of someOrdinal: '\0'
high value of someOrdinal: '\255'
value of someOrdinal 1 step away in decrementing order: @ (curr value: A)
value of someOrdinal 3 steps away in decrementing order: > (curr value: A)
value of someOrdinal 1 step away in incrementing order: B (curr value: A)
value of someOrdinal 3 steps away in incrementing order: D (curr value: A)
someOrdinal after incrementing once: B
someOrdinal after incrementing twice: D
someOrdinal after decrementing once: C
someOrdinal after decrementing 4 times: ?
value of someOrdinal: ?
integer value of someOrdinal: 63

Enums are ordinals too if their values are incrementing and continuous (incrementing by 1, no holes). So all of those methods for ordinals would work for such enums too.

import strformat, typetraits
type
  CompassDirections = enum
    cdNorth, cdEast, cdSouth, cdWest

for direction in ord(low(CompassDirections)) .. ord(high(CompassDirections)):
  echo fmt"{CompassDirections(direction)} ({$type(CompassDirections(direction))}), " &
       fmt"ord: {direction} ({$type(direction)})"

let cDir = cdEast
echo cDir.pred                  # int arg defaults to 1
echo cDir.succ(2)               # int arg defaults to 1
cdNorth (CompassDirections), ord: 0 (int)
cdEast (CompassDirections), ord: 1 (int)
cdSouth (CompassDirections), ord: 2 (int)
cdWest (CompassDirections), ord: 3 (int)
cdNorth
cdWest

CompassDirections(direction) is a type conversion that gives the CompassDirections enum from the integer direction.

It is possible to iterate through all possible values of ordinal enums, either as shown above, or cdNorth .. cdWest, which is equivalent.

Enums with holes #

Enums can also have disjoint values i.e. their values do not have to be incrementing by 1 in succession — I call them “holey enums”.

Enums with holes should not be used for any other reason than compatibility with C because it breaks the idea that enums are ordinals!

The Signals enum example in 18 is not an ordinal type enum as its values are non-continuous. As per the Nim Manual:

An explicit ordered enum can have holes.

However, it is then not an ordinal anymore, so it is not possible to use these enums as an index type for arrays. The procedures inc, dec, succ and pred are not available for them either.

TO BE FIXED Ordinal methods on enums with holes #

Contrary to the above quote from the Nim manual, below shows that inc, dec, succ, pred procedures do work with such “holey enums”; albeit printing “invalid data!” when those operations end up on holes – Nim Issue #8262, Nim Issue #1239.

type
  Signals = enum
    sigQuit = 3, sigAbort = 6, sigKill = 9

var nonOrdinal = sigQuit
echo "inc on holey enums"
echo " ", nonOrdinal
inc nonOrdinal
echo " ", nonOrdinal
inc nonOrdinal
echo " ", nonOrdinal
inc nonOrdinal
echo " ", nonOrdinal
inc nonOrdinal
echo " ", nonOrdinal
echo "dec on holey enums"
dec nonOrdinal
echo " ", nonOrdinal
dec nonOrdinal
echo " ", nonOrdinal
echo "succ on holey enums"
echo " ", nonOrdinal.succ
echo " ", nonOrdinal.succ(2)
echo "pred on holey enums"
echo " ", nonOrdinal.pred
inc on holey enums
 sigQuit
 4 (invalid data!)
 5 (invalid data!)
 sigAbort
 7 (invalid data!)
dec on holey enums
 sigAbort
 5 (invalid data!)
succ on holey enums
 sigAbort
 7 (invalid data!)
pred on holey enums
 4 (invalid data!)

DONE Distinct Types #

Distinct types are like type aliases, but they provide type safety so that it is impossible to coerce a distinct type into its base type without explicit conversion.

Below does not compile even if a is being assigned a float value, because the type of a is a distinct type alias of float. So float and Dollars are technically distinct types even if they are aliases.

type
  Dollars = distinct float

var a = Dollars(20)
a = 25.0  # Doesn't compile
nim_src_YftC2H.nim(8, 3) Error: type mismatch: got <float64> but expected 'Dollars = distinct float'

But the below compiles:

type
  Dollars = distinct float

var a = Dollars(20)
a = Dollars(25.0)
echo float(a)
25.0

Notice that in the above snippet, I used echo float(a). That’s because echo a will not work automatically!

None of the base type’s procedures will work for the distinct type – for example echo, +, *. As Dollars is a distinct type, its $ needs to be defined too!

So the below works:

type
  Dollars = distinct float

proc `$`(d: Dollars): string =
  $float(d) # convert Dollars to float and return stringified float

var a = Dollars(20)
a = Dollars(25.0)
echo a
25.0

If we do this, we will end up writing lots of thin wrappers for all such commonly used procedures. So another way is to use the {.borrow.} pragma instead, which automates the generation of such procedures borrowed from the base types.

type
  Dollars = distinct float

proc `$`(d: Dollars): string {.borrow.}
proc `*` *(a, b: Dollars): Dollars {.borrow.}
proc `+` *(a, b: Dollars): Dollars {.borrow.}

var a = Dollars(20)
a = Dollars(25.0)
echo a
a = 10.Dollars * (20.Dollars + 1.Dollars)
echo a
25.0
210.0

When creating a distinct type from an object type, none of its fields are carried over. If the fields are wanted, they can be brought over through an overloading of the {.borrow.} pragma. If they are not borrowed, they cannot be accessed.

Below example basically borrows the . operator from the base type Foo so that field access for the derived distinct type MyFoo works too.

type
  Foo = object
    a: int
  MyFoo {.borrow: `.`.} = distinct Foo

var value: MyFoo
echo value.a
0

Without that “dot” borrow, you would get this error:

nim_src_PlBYmp.nim(10, 11) Error: undeclared field: 'a'

DONE Strings #

There are several types of string literals:

Quoted Strings

Created by wrapping the body in triple quotes (""" .. """). They never interpret escape codes.

echo """
<html>
  <head>
  </head>\n\n

  <body>
  </body>
</html> """
<html>
  <head>
  </head>\n\n

  <body>
  </body>
</html>
Raw Strings

Created by prefixing the string with an r. They do not interpret escape sequences either, except for "", which is interpreted as ". This means that r"\b[a-z]\b" is interpreted literally as \b[a-z]\b.

echo r".""."
echo r"\b[a-z]\b"
.".
\b[a-z]\b
Proc Strings

Same as raw strings, but the proc name is prefixed directly to the string, so that foo"12" implies foo(r"12"). Note that the proc has to be called as PROC"STRING" i.e. no parentheses.

proc foo(s: string): string = s
echo foo".""."
# echo foo("."".") # This will not work; will give compilation error.
echo foo"\b[a-z]++\b"
1.".
1\b[a-z]++\b

Strings are null-terminated, so that cstring("foo") requires zero copying. However, you should be careful that the lifetime of the cstring does not exceed the lifetime of the string it is based upon.

Strings can also almost be thought of as seq[char] with respect to assignment semantics. See Seqs.

A note about Unicode #

Unicode symbols are allowed in strings, but are not treated in any special way, so if you want count glyphs or uppercase Unicode symbols, you must use the unicode module.

import strformat
let str = "કૌશલ"
echo fmt"str is just one unicode char {str}"
echo "Here is how it looks when str is parses char-by-char:"
echo fmt" str = {str}, number of chars = {str.len}"
for i, c in str:
  echo fmt"  char {i} = {repr(c)}"

import unicode
echo "Here is how it looks when str is parses rune-by-rune:"
echo fmt" str = {str}, number of Runes = {str.runeLen}"
for i, r in str.toRunes:
  echo fmt"  Rune {i} = {r}"
str is just one unicode char કૌશલ
Here is how it looks when str is parses char-by-char:
 str = કૌશલ, number of chars = 12
  char 0 = '\224'
  char 1 = '\170'
  char 2 = '\149'
  char 3 = '\224'
  char 4 = '\171'
  char 5 = '\140'
  char 6 = '\224'
  char 7 = '\170'
  char 8 = '\182'
  char 9 = '\224'
  char 10 = '\170'
  char 11 = '\178'
Here is how it looks when str is parses rune-by-rune:
 str = કૌશલ, number of Runes = 4
  Rune 0 = ક
  Rune 1 = ૌ
  Rune 2 = શ
  Rune 3 = લ

Strings are generally considered to be encoded as UTF-8. So because of Unicode’s backwards compatibility, they can be treated exactly as ASCII, with all values above 127 ignored.

DONE Arrays #

This section has more examples for Nim arrays. See Arrays for more info.

The size of arrays in Nim has to be specified at compile-time and cannot be given or changed at runtime.

The size of the array is encoded in its type and cannot be accidentally lost. Therefore, a procedure taking an array of variable length must encode the length in its type parameters. Alternatively, such procedures can also use openArrray as the parameter type, which the array length does not need to be specified.

type
  ThreeStringAddress = array[3, string]
let names: ThreeStringAddress = ["Jasmine", "Ktisztina", "Kristof"]
let addresses: ThreeStringAddress = ["101 Betburweg", "66 Bellion Drive", "194 Laarderweg"]
echo names
echo addresses

proc zip[I, T](a, b: array[I, T]):
               array[I, tuple[a, b: T]] =
  for i in low(a) .. high(a):
    result[i] = (a[i], b[i])

let nameAndAddresses = names.zip(addresses)
echo nameAndAddresses
["Jasmine", "Ktisztina", "Kristof"]
["101 Betburweg", "66 Bellion Drive", "194 Laarderweg"]
[(a: "Jasmine", b: "101 Betburweg"), (a: "Ktisztina", b: "66 Bellion Drive"), (a: "Kristof", b: "194 Laarderweg")]

The first type parameter of an array is actually a range like 0 .. N-1 (just a value “3” as in the above example is syntactic sugar for 0 .. 2).

It’s also possible to use ordinal values to index an array, effectively creating a lookup table. See Arrays with enum as length for more.

Nested Array #

type
  Matrix[W, H: static[int]] = array[1 .. W, array[1 .. H, int]]

let
  mat1: Matrix[2, 2] = [[1, 0],
                        [0, 2]]
  mat2: Matrix[2, 2] = [[0, 3],
                        [4, 0]]

proc `+`[W, H](a, b: Matrix[W, H]): Matrix[W, H] =
  for i in 1 .. high(a):
    for j in 1 .. high(a[0]):
      result[i][j] = a[i][j] + b[i][j]

echo mat1 + mat2
[[1, 3], [4, 2]]
  • static[int] in the above example is a static type. The static[T] construct is needed as the array size needs to be known at compile time. See Nim Manual – static{T} for more.

DONE Seqs #

Sequences (Seqs for short) provide dynamically expandable storage.

  • There are two ways to create sequences:
    • with the @ operator, and
    • with the newSeq[T](n: int) method
  • Once a sequence is created, it can be modified using methods like add(item: T) and delete(idx: int).
  • The length of a seq can be found through len: int, and the maximum index through high: int.
  • The standard items: T and pairs: tuple[i: int, v: T] iterators are also available.
var
  a = @[1, 2, 3]
  b = newSeq[int](3)
echo a

for i, v in a:
  b[i] = v*v
echo b

for i in 4 .. 15:
  b.add(i * i)
echo b

b.delete(0)  # takes O(n) time
echo b

b = a[0] & b  # Same as original b
echo b
@[1, 2, 3]
@[1, 4, 9]
@[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]
@[4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]
@[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]

Immutability #

Sequences are dynamically allocated (i.e. allocated on the heap, not the stack), but they are immutable (like any other Nim variable) unless marked as var.

So the below will fail to compile as a cannot be assigned to:

let a = @[1, 2, 3]
a.add(4)

However, the below will work without any problem:

var b = @[1, 2, 3]
b.add(4)
echo b
@[1, 2, 3, 4]

Sequences passed as “argument by value” are not modifiable. For example, the following will fail to compile:

proc doSomething(mySeq: seq[int]) =
  mySeq[0] = 2  # Error: 'mySeq[0]' cannot be assigned to

Sequence arguments can be mutable if they are passed as “argument by reference”, i.e. the parameter is annotated with var or ref:

proc foo(mySeq: var seq[int]) =
  mySeq[9] = 999

var thisSeq = newSeq[int](10)
foo(thisSeq)

echo thisSeq
@[0, 0, 0, 0, 0, 0, 0, 0, 0, 999]

If the sequence needs to be passed as value, and not reference, you can first copy that sequence inside the proc and then modify:

proc doSomething(mySeq: seq[int]) =
  var varMySeq = mySeq  # copy the seq
  varMySeq[0] = 999
  echo "Inside the proc: varMySeq = ", varMySeq

var testSeq = @[1, 2, 3]
echo "Before the proc: testSeq = ", testSeq
doSomething(testSeq)
echo "After the proc: testSeq = ", testSeq
Before the proc: testSeq = @[1, 2, 3]
Inside the proc: varMySeq = @[999, 2, 3]
After the proc: testSeq = @[1, 2, 3]

Above, as the doSomething proc is not returning the modified value varMySeq, the input argument sequence testSeq remains unmodified.

More about sequences #

See Sequences.

TODO Bitsets #

Check if a character is in a character set (in#

echo "'d' is lower? ", 'd' in {'a' .. 'z'}
echo "'D' is lower? ", 'D' in {'a' .. 'z'}
echo "'D' is upper? ", 'D' in {'A' .. 'Z'}
'd' is lower? true
'D' is lower? false
'D' is upper? true

Check if a character is not in a character set (notin#

echo "'d' is not lower? ", 'd' notin {'a' .. 'z'}
echo "'D' is not lower? ", 'D' notin {'a' .. 'z'}
echo "'D' is not upper? ", 'D' notin {'A' .. 'Z'}
echo "'.' is neither upper not lower? ", '.' notin {'A' .. 'Z', 'a' .. 'z'}
'd' is not lower? false
'D' is not lower? true
'D' is not upper? false
'.' is neither upper not lower? true

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.

Skipping tests #

Put skip or skip() at the end of the test block you want to skip.

import unittest

suite "something":
  test "passing test":
    check:
      true == true

  test "failing test to be ignored":
    check:
      true == false
    skip
[Suite] something
  [OK] passing test
    nim_src_bl1JT1.nim(13, 11): Check failed: true == false
    true was true
    false was false
  [SKIPPED] failing test to be ignored

Expecting failures (expect#

unittest – expect

import unittest

suite "something":
  test "passing test":
    check:
      true == true

  test "test expected to fail":
    expect AssertionError:
      assert true == false
[Suite] something
  [OK] passing test
  [OK] test expected to fail

Thanks to @mratsim from GitHub for this tip.

Also see #

Reference #

Documentation #

TODO runnableExamples #

See itertools library as an example.

Miscellaneous #

Checking if something compiles (compiles#

From docs:

Special compile-time procedure that checks whether x can be compiled without any semantic error. This can be used to check whether a type supports some operation.

when compiles(int):
  echo "int type exists"
when not compiles(Foo):
  echo "Foo type does not exist"

when compiles(3 + 4):
  echo "'+' for integers is available"
when not compiles(3 ... 4):
  echo "'...' for integers is not available"
int type exists
Foo type does not exist
'+' for integers is available
'...' for integers is not available

Thanks to @mratsim from GitHub for this tip.

Checking if an identifier is declared #

From the docs:

Special compile-time procedure that checks whether x is declared. x has to be an identifier or a qualified identifier. This can be used to check whether a library provides a certain feature or not.

The compile-time procedure declared returns true if a variable/type/proc/etc. is declared.

declared and variable #

declared(foo) returns true even if foo is not explicitly initialized.

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

declared and type #

declared is also used to check if something is a valid type.

type
  Foo = int
echo "Is 'Foo' declared? ", declared(Foo)
Is 'Foo' declared? true

This is useful to check for a type that could be introduced in a new Nim version – SomeFloat replaced the SomeReal type in Nim 0.18.1. So the below code can be used to define the new SomeFloat type only for older versions (without needing to do explicit version checks):

when not declared SomeFloat:
  type
    SomeFloat = SomeReal

Also see compiles.

declared and proc #

.. and the same for procs.

proc Foo() = discard
echo "Is 'Foo' declared? ", declared(Foo)
Is 'Foo' declared? true

Checking if a compilation switch is present (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.

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

Compiling in 32-bit mode #

Thanks to this tip by @Yardanico from GitHub, a Nim program can be compiled in 32-bit mode by doing:

nim c --cpu:i386 --passC:-m32 --passL:-m32 foo.nim

He provides a disclaimer to that saying “assuming your GCC is multilib”. I believe that’s the case – I don’t know! – because that worked 😄

From int types, one way to check if the compilation is happening in 32-bit vs 64-bit is to look at the size of the int type.

In 32-bit compilation:

import strformat
echo fmt"Size of int = {sizeof(int)} bytes"
Size of int = 4 bytes

In 64-bit compilation:

import strformat
echo fmt"Size of int = {sizeof(int)} bytes"
Size of int = 8 bytes

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"]

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.

borrow #

See Distinct Types.

Questions/Doubts #

NEED TO UNDERSTAND What are ptr and addr#

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

DONE 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.

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]