Emacs, scripting and anything text oriented.

Nim

Kaushal Modi

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.

Table of Contents
<2022-05-19 Thu>
Updated for Nim 1.7.1 (devel) again
<2021-11-01 Mon>
Updated for Nim 1.7.1 (devel)
<2021-05-27 Thu>
Updated for Nim 1.5.1 (devel)
<2019-11-01 Fri>
Updated for Nim 1.1.0
<2019-06-20 Thu>
Updated for Nim 0.20.0
<2019-01-02 Wed>
No need to import typetraits when just printing the type name, re-eval all code snippets using Nim devel as of today
<2018-10-17 Wed>
Overhaul all Nim doc links to point to the Devel docs version
<2018-09-27 Thu>
Verified all the code snippets for Nim 0.19.0

Nim Help #

If using Nim 0.19.0 or newer, 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
1.7.1
1
7
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,     ## underscored text
    styleBlink,          ## blinking/bold text
    styleBlinkRapid,     ## rapid blinking/bold text (not widely supported)
    styleReverse,        ## 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 std/[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 std/[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 std/[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 Code Snippet 1, except that the low-level procs setForeGroundColor, resetAttributes and stdout.write are used instead of stdout.styledWrite.

import std/[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

DONE When to use stdout.flushFile#

tl;dr Do buffer or file flushing frequently especially if the messages written to stdout/file are short.


From the reader Hasan in the comments below:

In my experience, regardless of the language, the input is buffered until the first EOL or a specific length is hit. So if you write something with a trailing EOL, you won’t need to flush, but if you have only a few characters, it is needed. I personally use it when I want to make sure that the output is sent, not buffered.

Also leorize from Nim IRC mentioned SO answer related to flushing buffers in C. The person Mike answering this says that using the below is very useful in C:

printf("Buffered, will be flushed");
fflush(stdout); // Prints to screen or whatever your standard out is

or

fprintf(fd, "Buffered, will be flushed");
fflush(fd); // Prints to a file

Here’s an excerpt from the rest of that answer talking about flushing stdout:

Why would you want to flush an output buffer?

Usually when I do it, it’s because the code is crashing and I’m trying to debug something. The standard buffer will not print everytime you call printf() it waits until it’s full then dumps a bunch at once. So if you’re trying to check if you’re making it to a function call before a crash, it’s helpful to printf something like “got here!”, and sometimes the buffer hasn’t been flushed before the crash happens and you can’t tell how far you’ve really gotten.

Another time that it’s helpful, is in multi-process or multi-thread code. Again, the buffer doesn’t always flush on a call to a printf(), so if you want to know the true order of execution of multiple processes you should fflush the buffer after every print.

I make a habit to do it, it saves me a lot of headache in debugging. The only downside I can think of to doing so is that printf() is an expensive operation (which is why it doesn’t by default flush the buffer).

Also, in the logging module docs, you see:

Warning: When logging on disk or console, only error and fatal messages are flushed out immediately. Use flushFile() where needed.

dump: An “echo” that auto-prints the expression and its value #

dump can be very instrumental for quick debugging. It saves you from writing a bunch of echo &"a = {a}" type of code – Now you just write dump a instead! 😍

import std/[sugar]
let
  a = 100
  b = 200
  c = (a, b)
dump a
dump b
dump (a + b)
dump (a / b)
dump c
Code Snippet 2: dump: A better echo for debugging
a = 100
b = 200
(a + b) = 300
(a / b) = 0.5
c = (100, 200)

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

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 #

NEED TO UNDERSTAND Quote Do #

Example of quote do #

Ref by @Vindaar from GitHub from here.

Once I understand Nim macros and quote do, I need to revisit this example to understand the quote do magic. - <2018-09-10 Mon>

import std/[macros]

type
  ShapeKind {.pure.} = enum
    Triangle, Rectangle

  Shape = object
    case kind: ShapeKind
    of Triangle:
      aT: int
      bT: int
    of Rectangle:
      aR: int
      bR: int

proc calcArea(s: Shape) =
  discard

proc createNext(c: NimNode): NimNode =
  let typ = c[0]
  let name = c[1]
  let a = c[2][0][0]
  let aVal = c[2][0][1]
  let b = c[2][1][0]
  let bVal = c[2][1][1]

  case $typ
  of "Triangle":
    a.ident = toNimIdent($a & "T")
    b.ident = toNimIdent($b & "T")
  of "Rectangle":
    a.ident = toNimIdent($a & "R")
    b.ident = toNimIdent($b & "R")
  result = quote do:
    let `name` = Shape(kind: `typ`, `a`: `aVal`, `b`: `bVal`)
    `name`.calcArea()

macro shape(input: untyped): untyped =
  ## should produce
  ##   let aaa = Shape(kind: Triangle, a: 17, b: 23)
  ##   aaa.calcArea()
  ##   let bbb = Shape(kind: Rectangle, a: 5, b: 8)
  ##   bbb.calcArea()
  result = newStmtList()
  echo input.treeRepr
  for i in 0 ..< input.len:
    let c = input[i]
    case c.kind
    of nnkCommand:
      result.add createNext(c)
    else:
      echo "Needs to be a Command!"

  echo result.repr


shape:
  Triangle aaa:
    a = 17
    b = 23
  Rectangle bbb:
    a = 5
    b = 8

TODO Quote Helper #

Nim Docs – Macros / quote

TODO Term Rewriting Macros #

Nim Manual – Term Rewriting Macros

Term rewriting macros (TRM) are macros or templates that have not only a name but also a pattern that is searched for after the semantic checking phase of the compiler. This means they provide an easy way to enhance the compilation pipeline with user defined optimizations.

import std/[strformat]

template optMul{`*`(a, 2)}(a: int): int =
  debugEcho("-> Term rewriting activated!")
  a + a

echo &"First arg = 3 ({$type(3)})"
echo "Calculating 3 * 2:" # Why isn't term rewriting activated here?
echo 3 * 2

let x = 3
echo ""
echo &"First arg = x ({$type(x)})"
echo "Calculating x * 2:"
echo x * 2

echo ""
echo &"First arg = 2 ({$type(2)})"
echo "Calculating 2 * x:"
echo 2 * x
First arg = 3 (int)
Calculating 3 * 2:
6

First arg = x (int)
Calculating x * 2:
-> Term rewriting activated!
6

First arg = 2 (int)
Calculating 2 * x:
6

Above, the compiler now rewrites x * 2 as x + x. The code inside the curlies ({`*`(a, 2)}) is the pattern to match against.

The operators *, **, |, ~ (see TRM pattern operators) have a special meaning in patterns if they are written in infix notation. So to match verbatim against * the ordinary function call syntax needs to be used.

TO BE FIXED TRM Side Effects #

Unfortunately optimizations are hard to get right. See the below tiny example that doesn’t work as expected. It’s causing the side effects in f proc to repeat too!

template optMul{`*`(a, 2)}(a: int): int =
  debugEcho("-> Term rewriting activated!")
  a + a

proc f(): int =
  echo "side effect!"
  result = 55

echo f() * 2
-> Term rewriting activated!
side effect!
side effect!
110

We should not duplicate a (the a parameter in that optMul TRM) if it denotes an expression that has a side effect!

Fortunately Nim knows whether or not side effects are there. So with the below, the TRM should not be getting activated if creating a has side effects.

template optMul{`*`(a, 2)}(a: int{noSideEffect}): int =
  debugEcho("-> Term rewriting activated!")
  a + a

proc f(): int =
  echo "side effect!"
  result = 55

echo f() * 2 # not optimized ;-)
side effect!
110

TO BE FIXED TRM Commutative #

You can make one overload matching with a constraint and one without, and the one with a constraint will have precedence, and so you can handle both cases differently.

So what about 2 * a? We should tell the compiler * is commutative.

We cannot really do that however as the following code only swaps arguments blindly. Below code will cause the TRM to be executed exactly (odd) 36 times, and then for later multiplications, the TRM doesn’t get activated at all! (see Nim Issue #9288):

template mulIsCommutative{`*`(a, b)}(a, b: int): int =
  debugEcho("-> Term rewriting activated!")
  b * a

let x = 3
echo "Calculating x * 2:"
echo x * 2

echo ""
echo "Calculating x * 2 again:"
echo x * 2

echo ""
echo "Calculating x * 3:"
echo x * 3
Calculating x * 2:
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
-> Term rewriting activated!
6

Calculating x * 2 again:
6

Calculating x * 3:
9

TRM Parameter Constraints #

The Nim Manual says:

What optimizers really need to do is a canonicalization.

I think that “canonicalization” means, make the TRM arg types less generic, so that that infloop can be prevented.

So by limiting the first arg to be a literal int, the canonMul TRM will be activated only if the first arg is a literal int (like 3, and not when it’s a variable holding int 3).

template canonMul{`*`(a, b)}(a: int{lit}, b: int): int =
  debugEcho("-> Term rewriting activated!")
  b * a

let
  x = 3
  y = 4
echo "The TRM won't be activated for 'x * y' because 'x' is not a literal int:"
echo x * y

echo ""
echo "The TRM will activated for '3 * y' because '3' *is* a literal int:"
echo 3 * y
The TRM won't be activated for 'x * y' because 'x' is not a literal int:
12

The TRM will activated for '3 * y' because '3' *is* a literal int:
-> Term rewriting activated!
12

The int{lit} parameter pattern matches against an expression of type int, but only if it’s a literal.

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-ord (or int)float$ 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 std/[strformat]
let
  b_seq = @[true, false]
for b in b_seq:
  var b_int = b.int
  echo &"Value of {$type(b)} b is {b}"
  echo &"Value of {$type(b_int)} 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 std/[strformat]
let b_seq = @[true, false]
for b in b_seq:
  var b_float = b.float
  echo &"Value of {$type(b)} b is {b}"
  echo &"Value of {$type(b_float)} 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 std/[strformat]
let b_seq = @[true, false]
for b in b_seq:
  var b_string1 = $b
  var b_string2 = &"{b}"
  var b_string3 = if b: "true" else: "false"
  echo &"Value of {$type(b)} b is {b}"
  echo &"  Value of {$type(b_string1)} b_string1 is {b_string1}"
  echo &"  Value of {$type(b_string2)} b_string2 is {b_string2}"
  echo &"  Value of {$type(b_string3)} 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 std/[strformat]
let
  c = 'A'
  c_int1 = c.ord
  c_int2 = c.int
echo &"Value of {$type(c)} c is {repr(c)}"
echo &"  Value of {$type(c_int1)} c_int1 is {c_int1}"
echo &"  Value of {$type(c_int2)} c_int2 is {c_int2}"
Value of char c is 'A'
  Value of int c_int1 is 65
  Value of int c_int2 is 65

As char is an Ordinal type, ord is very suitable for converting a char to int.

DONE char to float #

import std/[strformat]
let
  c = 'A'
  c_float = c.float
echo &"Value of {$type(c)} c is {repr(c)}"
echo &"Value of {$type(c_float)} c_float is {c_float}"
Value of char c is 'A'
Value of float c_float is 65.0

DONE char to string #

import std/[strformat, strutils]
let c_seq = @['A', 'b', '@']
for c in c_seq:
  let
    c_string1 = $c
    c_string2 = &"{c}"
    c_string3 = @[c].join("")
  var c_string4 = newString(1)
  c_string4[0] = c
  echo &"Value of {$type(c)} c is {repr(c)}"
  echo &"  Value of {$type(c_string1)} c_string1 is {c_string1}"
  echo &"  Value of {$type(c_string2)} c_string2 is {c_string2}"
  echo &"  Value of {$type(c_string3)} c_string3 is {c_string3}"
  echo &"  Value of {$type(c_string4)} 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 std/[strformat, strutils]
let
  c_seq = @['a', 'b', 'c', 'd']
  str = c_seq.join("")
echo &"{str} is the stringified form of {$type(c_seq)} {c_seq}"
abcd is the stringified form of seq[char] @['a', 'b', 'c', 'd']

From int #

DONE int to bool #

  • int value of 0 is false.
  • All other int values are true.
import std/[strformat]
let
  i_seq = @[-1, 0, 1, 2]
for i in i_seq:
  echo &"Value of {$type(i)} i is {i}"
  let
    i_bool = i.bool
  echo &"  Value of {$type(i_bool)} i_bool is {i_bool}"
Value of int i is -1
  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 2
  Value of bool i_bool is true

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 std/[strformat]
let i_seq = @[-1000, 0, 1, 1000]
for i in i_seq:
  echo &"Value of {$type(i)} i is {i}"
  var i_bool: bool
  try:
    i_bool = i.float.bool
    echo &"  Value of {$type(i_bool)} i_bool is {i_bool}"
  except:
    echo &"  [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.
  • RangeDefect exception is thrown if that value is outside that range.
import std/[strformat]
let i_seq: seq[int] = @[-1, 0, 62, 256]
for i in i_seq:
  var i_char1, i_char2: char
  echo &"Value of {$type(i)} i is {i}"
  try:
    i_char1 = chr(i) # or i.chr
    echo &"  Value of {$type(i_char1)} i_char1 is {repr(i_char1)}"
  except:
    echo &"  [Error] {getCurrentException().name}: {getCurrentException().msg}"
  try:
    i_char2 = i.char # or char(i)
    echo &"  Value of {$type(i_char2)} i_char2 is {repr(i_char2)}"
  except:
    echo &"  [Error] {getCurrentException().name}: {getCurrentException().msg}"
Value of int i is -1
  [Error] RangeDefect: value out of range: -1 notin 0 .. 255
  [Error] RangeDefect: value out of range
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] RangeDefect: value out of range: 256 notin 0 .. 255
  [Error] RangeDefect: value out of range

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 std/[strformat]
let i_seq = @[-2, -1, 0, 1, 254, 255, 256, 257]
for i in i_seq:
  var i_char = i.float.char
  echo &"Value of {$type(i)} i is {i}"
  echo &"  Value of {$type(i_char)} 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 std/[strformat]
let i_seq: seq[int] = @[-1, 0, 1000]
for i in i_seq:
  var i_float = i.float
  echo &"Value of {$type(i)} i is {i}"
  echo &"Value of {$type(i_float)} 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 std/[strformat, strutils]
let i_seq: seq[int] = @[-1000, 0, 1000]
for i in i_seq:
  echo &"Value of {$type(i)} i is {i}"
  var
    i_str1 = $i
    i_str2 = &"{i}"
    i_str3 = intToStr(i) # strutils
  echo &"  Value of {$type(i_str1)} i_str1 is {i_str1}"
  echo &"  Value of {$type(i_str2)} i_str2 is {i_str2}"
  echo &"  Value of {$type(i_str3)} 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 std/[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 macro & from strformat. See below to see what I mean by “similar”.

import std/[strformat]
let i_arr = [-100, -50, 0, 0, 123, 1000]
for i in i_arr:
  echo &"{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 #

  • float value of 0.0 is false.
  • All other float values are true.
import std/[strformat]
let
  f_seq = @[-1.1, -0.5, 0.0, 0.5, 1.1]
for f in f_seq:
  var f_bool = f.bool
  echo &"Value of {$type(f)} f is {f:>5.2f} => Value of {$type(f_bool)} f_bool is {f_bool}"
Value of float64 f is -1.10 => Value of bool f_bool is true
Value of float64 f is -0.50 => Value of bool f_bool is true
Value of float64 f is  0.00 => Value of bool f_bool is false
Value of float64 f is  0.50 => Value of bool f_bool is true
Value of float64 f is  1.10 => 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 std/[strformat]
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 &"Value of {$type(f)} f is {f}"
  echo &"Value of {$type(f_char)} 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 std/[strformat, 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 &"Value of {$type(f)} f is {f}"
  echo &"Value of {$type(f_int)} 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 std/[strformat]
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 = &"{f}"
  echo &"Value of {$type(f)} f is {f}"
  echo &"  Value of {$type(f_string1)} f_string1 is {f_string1}"
  echo &"  Value of {$type(f_string2)} 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 std/[strformat, strutils]
let s_seq = @["true", "True", "tRuE", "false", "False", "FaLsE"]
for s in s_seq:
  var s_bool = parseBool(s)
  echo &"Value of {$type(s)} s is {s}"
  echo &"Value of {$type(s_bool)} 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 std/[strformat]
let s = "abcd\n"
var c_seq: seq[char]
for c in s:
  c_seq.add(c)
echo &"Value of {$type(s)} s is {s}"
echo &"Value of {$type(c_seq)} c_seq is {c_seq}"
for i, c in c_seq:
  echo &" c_seq[{i}] = {repr(c)} (type: {$type(c)})"
Value of string s is abcd

Value of seq[char] c_seq is @['a', 'b', 'c', 'd', '\n']
 c_seq[0] = 'a' (type: char)
 c_seq[1] = 'b' (type: char)
 c_seq[2] = 'c' (type: char)
 c_seq[3] = 'd' (type: char)
 c_seq[4] = '\10' (type: char)

DONE string to int #

import std/[strformat, strutils]
let
  s = "1212"
  s_int = parseInt(s)
echo &"Value of {$type(s)} s is {s}"
echo &"Value of {$type(s_int)} s_int is {s_int}"
Value of string s is 1212
Value of int s_int is 1212

DONE string to float #

import std/[strformat, strutils]
let
  s = "12.12"
  s_float = parseFloat(s)
echo &"Value of {$type(s)} s is {s}"
echo &"Value of {$type(s_float)} 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 std/[strformat]
echo &"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 std/[strformat]
echo &"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 std/[strformat]
var
  aInt: int = 1
  aInt8: int8 = 2
  aInt16: int16 = 3
  aInt32: int32 = 4
  aInt64: int64 = 5
echo &"aInt64 = {aInt64}, aInt32 = {aInt32}, aInt16 = {aInt16}, aInt8 = {aInt8}, aInt = {aInt}"
aInt64 = aInt8 # works
aInt64 = aInt16 # works
aInt64 = aInt32 # works
aInt64 = aInt # works
echo &"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 &"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 &"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 &"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.ord, " = ", 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 std/[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 &"int:     {low(int)} (-2^{int_low_2power}) -> {high(int)} (2^{int_low_2power} - 1)"
echo &"int32:   {low(int32)} (-2^{int32_low_2power}) -> {high(int32)} (2^{int32_low_2power} - 1)"
echo &"int64:   {low(int64)} (-2^{int64_low_2power}) -> {high(int64)} (2^{int64_low_2power} - 1)"
echo &"float:   {low(float)} -> {high(float)}"
echo &"float32: {low(float32)}  -> {high(float32)}"
echo &"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 std/[strformat]
echo &"Size of bool is {sizeof(bool)} byte"
echo &"Size of char is {sizeof(char)} byte"
echo &"Size of int is {sizeof(int)} bytes"
echo &"Size of int32 is {sizeof(int32)} bytes"
echo &"Size of int64 is {sizeof(int64)} bytes"
echo &"Size of float is {sizeof(float)} bytes"
echo &"Size of float32 is {sizeof(float32)} bytes"
echo &"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 non-string primitive types #

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

import std/[strformat]
var
  b: bool
  c: char
  i: int
  f: float
echo &"initial value of {$type(b)} b = {b}, isNil? {b.isNil}"
echo &"initial value of {$type(c)} c = {c}, isNil? {c.isNil}"
echo &"initial value of {$type(i)} i = {i}, isNil? {i.isNil}"
echo &"initial value of {$type(f)} 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(315, 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)
/bin/sh: /tmp/babel-2oW0wY/nim_src_FoNExx: Permission denied

isNil on string #

isNil does not work on strings too (after the removal of nil as a valid string value in Nim 0.19.0+).

var s: string
echo s.isNil()

Trying to do so will give this error:

nim_src_23lLuf.nim(6, 8) Error: usage of 'isNil' is a user-defined error

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

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 std/[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 std/[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.

fmt has some limitations compared to its equivalent macro & (from the same strformat library). Read here for more. So I prefer to use & consistently everywhere instead of fmt. Unless mentioned otherwise, whatever is written about strformat.fmt applies to the strformat.& too.

import std/[strformat]
let
  a = 100
  b = "abc"
echo &"a = {a}, b = {b}"
a = 100, b = abc
TO BE FIXED Use string variable containing message formatting for fmt #

Below does not work:

import std/[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:

# Let's say NimVersion is "0.20.99"
assert "0.20.99" >= "0.19.0"
assert "0.20.99" < "00.19.0" # Surprise!
assert "0.20.99" >= "0.09.0"
assert "0.20.99" < "0.9.0" # Surprise!

See Tuple comparison instead for Nim version comparison.

Unicode #

Rune #

Just as plain ASCII strings are composed of chars, strings with Unicode characters (>8-bits) are composed of Runes.

  • 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.
import std/[unicode]
from std/strutils import isAlphaNumeric, isAlphaAscii
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

Walking through chars vs Runes #

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 std/[strformat]

let str = "કૌશલ"

echo "Here is how it looks when str is parsed char-by-char:"
echo &" str = {str}, number of chars = {str.len}"
for i, c in str:
  echo &"  char {i} = {repr(c)} ({ord(c):#x})"
Code Snippet 3: Walking through chars in a string
Here is how it looks when str is parsed char-by-char:
 str = કૌશલ, number of chars = 12
  char 0 = '\224' (0xe0)
  char 1 = '\170' (0xaa)
  char 2 = '\149' (0x95)
  char 3 = '\224' (0xe0)
  char 4 = '\171' (0xab)
  char 5 = '\140' (0x8c)
  char 6 = '\224' (0xe0)
  char 7 = '\170' (0xaa)
  char 8 = '\182' (0xb6)
  char 9 = '\224' (0xe0)
  char 10 = '\170' (0xaa)
  char 11 = '\178' (0xb2)

import std/[unicode, strformat]

let str = "કૌશલ"

echo "Here is how it looks when str is parsed rune-by-rune:"
echo &" str = {str}, number of Runes = {str.runeLen}"
for i, r in str.toRunes:
  echo &"  Rune {i} = {r}"
Code Snippet 4: Walking through Runes in a string
Here is how it looks when str is parsed 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.

Unidecode #

unidecode is a Nim stdlib. It is used to transliterate unicode chars (non-English languages, etc.) to English ASCII characters – Ref.

When importing unidecode, most likely you would want to compile your Nim code with the -d:embedUnidecodeTable switch.

import std/[unidecode/unidecode] # or just import unidecode

echo unidecode("મારુ નામ કૌશલ મોદી છે. હૂં અમદાવાદ થી છું.")
echo unidecode("Æneid")
echo unidecode("北京")
# Below is the same as above
echo unidecode("\xe5\x8c\x97\xe4\xba\xac")
Code Snippet 5: Example of transliteration using unidecode (compiled with -d:embedUnidecodeTable)
maaru naam kaushl modii che. huuN amdaavaad thii chuN.
AEneid
Bei Jing
Bei Jing

In Emacs, with point over 北, if I do C-u C-x =, I get:

buffer code: #xE5 #x8C #x97

So I translated that to \xe5\x8c\x97, and then the same for 京.

If the embedUnidecodeTable symbol is not defined during compilation, an explicit call of the loadUnidecodeTable proc is needed, with the path to the unidecode.dat file as its argument.

Below code is compiled without the -d:embedUnidecodeTable switch.

  • So the findNimStdLib proc is first defined to find the path to the installed Nim standard libraries, and based on that, the path to the inbuilt unidecode.dat is derived.
  • Then loadUnidecodeTable proc is used to load that inbuilt table.

import std/[unidecode/unidecode] # or just import unidecode

import std/[os]
proc findNimStdLib*(): string =
  ## Tries to find a path to a valid "system.nim" file.
  ## Returns "" on failure.
  try:
    let nimexe = os.findExe("nim")
    if nimexe.len == 0: return ""
    result = nimexe.splitPath()[0] /../ "lib"
    if not fileExists(result / "system.nim"):
      when defined(unix):
        result = nimexe.expandSymlink.splitPath()[0] /../ "lib"
        if not fileExists(result / "system.nim"): return ""
  except OSError, ValueError:
    return ""

# Load the Unicode data file.
loadUnidecodeTable(findNimStdLib() / "pure/unidecode/unidecode.dat")

echo unidecode("મારુ નામ કૌશલ મોદી છે. હૂં અમદાવાદ થી છું.")
echo unidecode("Æneid")
echo unidecode("北京")
# Below is the same as above
echo unidecode("\xe5\x8c\x97\xe4\xba\xac")
Code Snippet 6: Example of transliteration using unidecode (compiled without -d:embedUnidecodeTable)
maaru naam kaushl modii che. huuN amdaavaad thii chuN.
AEneid
Bei Jing
Bei Jing

See Nim StdLib path for the source of findNimStdLib.

Math #

Random #

Call randomize before rand #

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

import std/[random]
echo rand(high(int))
Code Snippet 7: rand without randomize
4292486321577947087

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

import std/[random]
randomize()
echo rand(high(int))
Code Snippet 8: rand with randomize, no seed
8191563575897872325

Specifying randomization seed #

There are many use cases where you want to specify a particular randomization seed so that the randomized values are reproducible.

Specifying randomization seed using randomize #

One way is to pass an integer seed to the randomize function.

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

import std/[random]
randomize(123) # arbitrary integer seed
echo rand(high(int))
Code Snippet 9: Using randomize to specify a randomization seed
3695677889614723175
FIXED 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.

<2019-02-19 Tue> This was indeed odd, and is now fixed in 304b1dd34bc52.

import std/[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))

After the fix, above code gives this error:

Error: unhandled exception: /home/kmodi/usr_local/apps/6/nim/devel/lib/pure/random.nim(516, 12) `seed != 0`  [AssertionError]

But earlier, it gave this strange output:

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
Getting the same Rand state using initRand #

The initRand proc returns the same Rand state for the same provided integer seed. See initRand.

import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

echo rs.rand(high(int))
Code Snippet 10: Using initRand to get the same Rand state each time
4611335679820174795

Random bool #

import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

for _ in 0 .. 5:
  echo rs.rand(1).bool
true
false
true
false
false
true

Random range #

Use rand(LOWER .. UPPER).

import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

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

Works with negatives too:

import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

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

rand range #

import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

let k = 3

for _ in 0 .. (2*k):
  echo rs.rand(k)
3
0
1
0
0
1
3

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 std/[random, strformat]
echo &"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 std/[random, strformat]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

echo &"rand(9223372036854775807.int) = {rs.rand(9223372036854775807.int)}"
rand(9223372036854775807.int) = 4611335679820174795

Log #

log #

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

import std/[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 std/[random, strformat]
echo &"log10(111) = {log(111.float, 10.float)}"
echo &"log2(8) = {log(8.float, 2.float)}"
echo &"ln(8) = {log(8.float, E)}"
echo &"ln(8) = {log(8.float)}"
log10(111) = 2.045322978786657
log2(8) = 3.0
ln(8) = 2.079441541679836
ln(8) = 2.079441541679836

log2 #

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

log10 #

import std/[math, strformat]
var
  val = 100
  power10 = log10(val.float) # need to cast val to a float
echo &"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 std/[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 std/[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 std/[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 std/[math]
echo pow(2.0, 1.0)
echo pow(2.float, 1.float) # same as above
2.0
2.0

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 std/[strformat]
echo "divisor = 3"
for i in -4 .. 4:
  echo &"  {i:2} div  3 = {i div 3:2}"
echo "\ndivisor = -3"
for i in -4 .. 4:
  echo &"  {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 std/[strformat]
echo "divisor = 3"
for i in -4 .. 4:
  echo &"  {i:2} mod  3 = {i mod 3:2}"
echo "\ndivisor = -3"
for i in -4 .. 4:
  echo &"  {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

Bitwise operations #

Bitwise And #

import std/[strformat]
let
  a = 15
  b = 1
var
  c = (a + b) and 0x0f
echo &"4-bit addition (overflow): {a:#03x} + {b:#03x} = {c:#03x}"
echo &"4-bit addition (overflow): {a:#06b} + {b:#06b} = {c:#06b}"
Code Snippet 11: Limiting output to 4-bits
4-bit addition (overflow): 0xf + 0x1 = 0x0
4-bit addition (overflow): 0b1111 + 0b0001 = 0b0000

Variable “Types” #

Table 2: var vs let vs const
KeywordVariable typeMust be initialized?Can be set during runtime?Should be evaluable 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

“Python 3.8”-like Walrus operator or when-let in Emacs-Lisp #

ref

proc test0(): int =
  return 0

proc test1(): int =
  return 1

proc main() =
  if (var x = test0(); x) == 1:
    echo "Is 1"
  else:
    echo "Not 1"

  if (var x = test1(); x) == 1:
    echo "Is 1"
  else:
    echo "Not 1"

main()
Not 1
Is 1

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 12: 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 13: 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 14: 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': @[]

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 15: 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 std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

proc polluteStack() =
  var
    c: char = (rs.rand(high(char).int)).char
    i32: int32 = (rs.rand(high(int32).int)).int32
    i64: int64 = (rs.rand(high(int64).int)).int64
    f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rs.rand(high(int64).int)).float64
    b: bool = rs.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 = (rs.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 (rs.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 std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

proc polluteStack() =
  var
    c: char = (rs.rand(high(char).int)).char
    i32: int32 = (rs.rand(high(int32).int)).int32
    i64: int64 = (rs.rand(high(int64).int)).int64
    f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rs.rand(high(int64).int)).float64
    b: bool = rs.rand(1).bool
  echo repr(c)
  echo i32
  echo i64
  echo f32
  echo f64
  echo b
polluteStack()
'\203'
713337728
7622359537808702249
6.732090894058521e+18
5.509501416271037e+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 std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

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

# local var in block
block foo:
  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 std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

proc polluteStack() =
  var
    c: char = (rs.rand(high(char).int)).char
    i32: int32 = (rs.rand(high(int32).int)).int32
    i64: int64 = (rs.rand(high(int64).int)).int64
    f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rs.rand(high(int64).int)).float64
    b: bool = rs.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

polluteStack()
a()
Code Snippet 16: noinit pragma works for local variables
Value of uninitialized variable 'ui_i1': 203
Value of uninitialized variable 'ui_i2': [3063762216384782021, 713337728, -3783521239797778229]
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 std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

proc polluteStack() =
  var
    c: char = (rs.rand(high(char).int)).char
    i32: int32 = (rs.rand(high(int32).int)).int32
    i64: int64 = (rs.rand(high(int64).int)).int64
    f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rs.rand(high(int64).int)).float64
    b: bool = rs.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

polluteStack()
a()
[3063762216384782021, 713337728, -3783521239797778229]
['\x0F', '\xC8', 'i']
[false, false, false]
[2.146009457924067e+148, 1.11242189140595e+142, 3.683500169218559e+201]
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 std/[random, math]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

proc polluteStack() =
  var
    c: char = (rs.rand(high(char).int)).char
    i32: int32 = (rs.rand(high(int32).int)).int32
    i64: int64 = (rs.rand(high(int64).int)).int64
    f32: float32 = (rs.rand(high(int64).int)).float32 # Intentionally using high(int64); not a typo
    f64: float64 = (rs.rand(high(int64).int)).float64
    b: bool = rs.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

polluteStack()
bar()
i32 (auto-init) = 0
i64 (auto-init) = 0
f32 (auto-init) = 0.0
f64 (auto-init) = 0.0
i32ni (no init) = 1774718877
i64ni (no init) = 7622359537808702249
f32ni (no init) = 1.068217052948333e+18
f64ni (no init) = 2.146009457924067e+148

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

Heterogeneous Slice (HSlice) #

The heterogeneous slice type is an object of 2 elements – which can be of different types (and thus.. heterogeneous).

HSlice[T; U] = object
  a*: T                        ## the lower bound (inclusive)
  b*: U                        ## the upper bound (inclusive)

An HSlice can be created using below:

import std/[strformat]
var s1: HSlice[int, char]
s1.a = 4
s1.b = 'f'
echo &"s1=`{s1}' is of type {$type(s1)}"
s1=`4 .. f' is of type HSlice[system.int, system.char]

But that verbose style of assignment doesn’t look like a lot of fun. So the double-dot operator .. is defined to quickly create HSlices. Below snippet creates the exact same HSlice as in the above snippet:

import std/[strformat]
let s1 = 4 .. 'f'
echo &"s1=`{s1}' is of type {$type(s1)}"
s1=`4 .. f' is of type HSlice[system.int, system.char]

The HSlices are very commonly used with both elements having the same type (that too, usually int or char) — in slice iterators, or specify a range of numbers to randomize inbetween, or to get a slice of a string, or a sequence or array. Also see Slice.

import std/[strformat]

let hs1 = 1 .. 3
echo &"hs1=`{hs1}' is of type {$type(hs1)}"
for n in hs1:
  echo n
let
  hs2 = 'k' .. 'm'
echo &"hs2=`{hs2}' is of type {$type(hs2)}"
for c in hs2:
  echo c

import std/[random]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

echo "random num: ", rs.rand(8 .. 9)

let str = "abcdefghijk"
echo str[2 .. 4]
Code Snippet 17: HSlice: Slice iterators, random range, string slice
hs1=`1 .. 3' is of type HSlice[system.int, system.int]
1
2
3
hs2=`k .. m' is of type HSlice[system.char, system.char]
k
l
m
random num: 9
cde

But by its definition, an HSlice can have elements of different types too:

import std/[strformat]
let
  hs3 = 1 .. 'z'
  hs4 = 0.99 .. "blah"
echo &"hs3=`{hs3}' is of type {$type(hs3)}"
echo &"hs4=`{hs4}' is of type {$type(hs4)}"
Code Snippet 18: HSlice: Heterogeneous types
hs3=`1 .. z' is of type HSlice[system.int, system.char]
hs4=`0.99 .. blah' is of type HSlice[system.float64, system.string]

As the string slice notation uses HSlice, and the HSlice elements can be of different types, below works too:

let str = "abcdefghijk"
type
  MyEnum = enum
    Sixth = 5
    Seventh
echo str[3 .. Sixth]
Code Snippet 19: HSlice: String slice with heterogeous type elements
def

Here are few examples of nested HSlices:

import std/[strformat]
let
  hs5 = 777 .. 'd' .. "foo"
  hs6 = (777 .. 'd') .. "foo" # same as above
  hs7 = 5 .. 6 .. 'x' .. 'y'
  hs8 = ((5 .. 6) .. 'x') .. 'y' # same as above
echo &"hs5=`{hs5}' is of type {$type(hs5)}"
echo &"hs6=`{hs6}' is of type {$type(hs6)}"
echo &"hs7=`{hs7}' is of type {$type(hs7)}"
echo &"hs8=`{hs8}' is of type {$type(hs8)}"
Code Snippet 20: HSlice: Nested
hs5=`777 .. d .. foo' is of type HSlice[HSlice[system.int, system.char], system.string]
hs6=`777 .. d .. foo' is of type HSlice[HSlice[system.int, system.char], system.string]
hs7=`5 .. 6 .. x .. y' is of type HSlice[HSlice[HSlice[system.int, system.int], system.char], system.char]
hs8=`5 .. 6 .. x .. y' is of type HSlice[HSlice[HSlice[system.int, system.int], system.char], system.char]

As it can be seen above, things can get crazy too quickly, so use parentheses to break up the HSlices as needed – Below is an HSlice of two different types of HSlices:

import std/[strformat]
let
  hs9 = (5 .. 6) .. ('x' .. 'y')
echo &"hs9=`{hs9}' is of type {$type(hs9)}"
Code Snippet 21: HSlice: Nested, with parentheses
hs9=`5 .. 6 .. x .. y' is of type HSlice[HSlice[system.int, system.int], HSlice[system.char, system.char]]

HSlice / Slice bounds #

  • Use .a and .b respectively to get the left and right bounds of an HSlice.
  • Note that a is not necessarily less than b, in fact, they can be equal too.

import std/[strformat]
let
  s1 = -100 .. 100  # a < b
  s2 = 10 .. 1      # a > b
  s3 = 7 .. 7       # a == b
echo &"s1=`{s1}' is of type {$type(s1)}."
echo &"  Left bound = {s1.a}, right bound = {s1.b}"
echo &"  Low bound = {min(s1.a, s1.b)}, high bound = {max(s1.a, s1.b)}"
echo &"s2=`{s2}' is of type {$type(s2)}."
echo &"  Left bound = {s2.a}, right bound = {s2.b}"
echo &"  Low bound = {min(s2.a, s2.b)}, high bound = {max(s2.a, s2.b)}"
echo &"s3=`{s3}' is of type {$type(s3)}."
echo &"  Left bound = {s3.a}, right bound = {s3.b}"
echo &"  Low bound = {min(s3.a, s3.b)}, high bound = {max(s3.a, s3.b)}"
Code Snippet 22: HSlice bounds
s1=`-100 .. 100' is of type HSlice[system.int, system.int].
  Left bound = -100, right bound = 100
  Low bound = -100, high bound = 100
s2=`10 .. 1' is of type HSlice[system.int, system.int].
  Left bound = 10, right bound = 1
  Low bound = 1, high bound = 10
s3=`7 .. 7' is of type HSlice[system.int, system.int].
  Left bound = 7, right bound = 7
  Low bound = 7, high bound = 7

(Homogeneous) Slice #

A Slice is an alias for HSlice[T, T] i.e. it is a “heterogeneous” slice where both of the elements are of the same type.

Below snippet shows that the HSlice[int, int] and Slice[int] are the same (as we can assign value from one of those types to another):

import std/[strformat]
let
  s1: Slice[int] = 4 .. 6
echo &"s1=`{s1}' is of type {$type(s1)}"
let
  s2: HSlice[int, int] = s1
echo &"s2=`{s2}' is of type {$type(s2)}"
Code Snippet 23: Slice: HSlice with same types
s1=`4 .. 6' is of type Slice[system.int]
s2=`4 .. 6' is of type HSlice[system.int, system.int]

Attempting to assign an HSlice with different-type elements to a Slice will give compilation error:

let
  s1: Slice[int] = 4 .. 'f'
nim_src_LLOTy4.nim(5, 22) Error: type mismatch: got <HSlice[system.int, system.char]> but expected 'Slice[system.int]'

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 std/[strformat]
let
  arr1 = [1.0, 2, 3, 4]
  arr2 = ['a', 'b', 'c', 'd']
  arr3 = ["a", "bc", "def", "ghij", "klmno"]
echo &"arr1 is of type {$type(arr1)} with value {arr1}"
echo &"arr2 is of type {$type(arr2)} with value {arr2}"
echo &"arr3 is of type {$type(arr3)} 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"]
Unchecked Array #

Thanks to shashlick from Nim IRC for the below example [ ref ]:

var
  test = alloc0(sizeof(int) * 4)
  test2 = cast[ptr UncheckedArray[int]](test)

test2[1] = 5
test2[3] = 11

#echo test2[] # doesn't work .. probably a bug?

for i in 0 .. 3:
  echo test2[i]
Code Snippet 24: Unchecked Array
0
5
0
11

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 std/[strformat]
let
  i_seq1:seq[int] = @[1, 2, 3, 4, 5, 6]
  i_seq2 = @[7.0, 8, 9]
echo &"i_seq1 is of type {$type(i_seq1)} with value {i_seq1}"
echo &"i_seq2 is of type {$type(i_seq2)} 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 std/[strformat]
var i_seq = @[1, 2, 3]
echo &"i_seq is of type {$type(i_seq)} with value {i_seq}"
echo &"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 &"  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 &"  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 &"  popped_elem = {popped_elem}"
echo &"  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

Reversing Sequences #

Use reversed proc from the stdlib algorithm. It takes in arrays, sequences and strings, but always returns sequences.

import std/[algorithm]
echo reversed([1, 2, 3]) # array
echo reversed(@[1, 2, 3]) # sequence
echo reversed("abc") # string
@[3, 2, 1]
@[3, 2, 1]
@['c', 'b', 'a']

Note that the string “abc” converts to a reversed sequence of chars composing that string. If you want a reversed string, just join those chars again:

import std/[algorithm, strutils]
echo reversed("abc").join("")
cba

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 std/[strformat]
proc testOpenArray(x: openArray[int]) =
  echo &"x = {x} (type: {$type(x)}, 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 std/[strformat]
proc foo(x: openArray[int]) =
  echo &"{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
    
    ("Peter", 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 std/[strformat]
let person: (string, int) = ("Peter", 30)
echo &"Tuple person of type {$type(person)} = {person}"
echo person[0]
echo person[1]
Tuple person of type (string, int) = ("Peter", 30)
Peter
30
Named tuples #
import std/[strformat]
type
  Person = tuple
    name: string
    age: int
let person: Person = ("Peter", 30)
echo &"Tuple person of type {$type(person)} = {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
Check if a field exists #

Use compiles. If the argument is a tuple variable with valid field reference, it will return trueRef.

Named tuples #

type
  Person = tuple
    name: string
    age: int
let p: Person = ("Peter", 30)
echo compiles(p.age)
echo compiles(p.foo)            # invalid field
echo compiles(p[0])
echo compiles(p[1])
echo compiles(p[2])          # invalid field.. tuple has only 2 fields
Code Snippet 25: Checking if fields exist in a named-tuple variable
true
false
true
true
false

Above, the tuple variable p is passed to compiles.

You can instead pass the tuple type Person too. This is useful if you want to check if a tuple field exists before creating a variable of its type. But then, only named field check worksRef.

type
  Person = tuple
    name: string
    age: int
echo compiles(Person.age)
echo compiles(Person.foo)       # invalid field
echo compiles(Person[0]) # incorrectly returns 'false', compared to compiles(p[0]) in previous snippet
Code Snippet 26: Checking if fields exist in a named-tuple type
true
false
false

Do not use a tuple type identifier with field index reference when checking if that field exists.

Here’s why compiles(Person.age) works, but compiles(Person[0]) doesn’t – Person.age returns its type, int, but Person[0] causes compilation failure:

nim_src_AVBMhm.nim(10, 12) Error: no generic parameters allowed for Person

type
  Person = tuple
    name: string
    age: int
echo Person.age
# echo Person[0] # causes compilation error
int

The compilation error makes sense, because a tuple type can be generic too!

Anonymous tuples #

let q: (string, int, float) = ("abc", 42, 3.117)
echo compiles(q[0])
echo compiles(q[1])
echo compiles(q[2])
echo compiles(q[3])          # invalid field.. tuple has only 3 fields
Code Snippet 27: Checking if fields exist in an anonymous-tuple variable
true
true
true
false

For the reason explained in above section, it is possible to check if a tuple field exists, by using its index, but only when using the tuple variable identifier, and not its type.

Finding number of tuple fields #

As suggested by @Vindaar from GitHub, arity from typetraits can be used to get the number of fields in a tuple – Ref.

import std/[strformat, typetraits]
type
  Person = tuple
    name: string
    age: int
let
  p: Person = ("Peter", 30)
  q: (string, int, float) = ("abc", 42, 3.117)
echo &"Number of fields in p = {p.type.arity}, value = {p}"
echo &"Number of fields in q = {q.type.arity}, value = {q}"
Code Snippet 28: Number of tuple fields
Number of fields in p = 2, value = (name: "Peter", age: 30)
Number of fields in q = 3, value = ("abc", 42, 3.117)
Looping through fields #

import std/[strutils]
type
  MyTupNamed = tuple
    x: int
    y: int
let
  tupNamed = (100, 200).MyTupNamed
  tupAnon = (9, "abc")

echo "Looping through tupNamed fields .."
for val in tupNamed.fields:
  echo "  ", val
for f, val in tupNamed.fieldPairs:
  echo "  tupNamed.$1 = $2" % [f, $val]

echo "Looping through tupAnon fields .."
for val in tupAnon.fields:
  echo "  ", val
for f, val in tupAnon.fieldPairs:
  echo "  tupAnon.$1 = $2" % [f, $val]
Code Snippet 29: Looping through tuple fields
Looping through tupNamed fields ..
  100
  200
  tupNamed.x = 100
  tupNamed.y = 200
Looping through tupAnon fields ..
  9
  abc
  tupAnon.Field0 = 9
  tupAnon.Field1 = abc

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 std/[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 #

Comparing one named tuple to another #

Different named 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)

You don’t need to declare a type for a tuple to use it. – like the example of teacher variable in the above example.

Named tuples created with different field names will be considered different objects despite having same number of fields of the same 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]'
Comparing named tuple and anonymous tuple types #

From the following example, 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 std/[strformat]
type Person = tuple[name: string, age: int]
let personAnon1 = ("Peter", 30)
var
  personNamed: Person
  personAnon2: (string, int)
echo &"Tuple personAnon1 of type {$type(personAnon1)} = {personAnon1}"

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

echo "Assigning a named tuple to an anonymous tuple .."
personAnon2 = personNamed
echo &"Tuple personAnon2 of type {$type(personAnon2)} = {personAnon2}"
Tuple personAnon1 of type (string, int) = ("Peter", 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) = ("Peter", 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 personAnon1 didn’t get the field names as it got assigned using a direct value without specifying the tuple type.

Tuple comparison #

const
  nimVersion = (major: NimMajor, minor: NimMinor, patch: NimPatch)
echo nimVersion

assert nimVersion > (0, 18, 0)
assert nimVersion >= (1, 1, 0)
assert nimVersion < (2, 0, 0)
Code Snippet 30: Checking Nim version using tuple comparison
(major: 1, minor: 7, patch: 1)

Generic Tuples #

A tuple can be a generic too!

Thanks to @Vindaar from GitHub and TheLemonMan on IRC to help me understand this – Ref.

type
  Person[N: static[int]] = tuple
    name: string
    childrenAges: array[N, int]

let
  p0: Person[0] = ("Peter", [])
  p1: Person[1] = ("Peter", [1])
  p2: Person[2] = ("Peter", [1, 2])
  p3: Person[3] = ("Peter", [1, 2, 3])
echo p0
echo p1
echo p2
echo p3
Code Snippet 31: A generic tuple
(name: "Peter", childrenAges: [])
(name: "Peter", childrenAges: [1])
(name: "Peter", childrenAges: [1, 2])
(name: "Peter", childrenAges: [1, 2, 3])

TODO Objects #

TODO Looping through object keys #

fields and fieldPairs #

TODO Pointers and References #

TODO Pointer (ptr#

Dereferencing #

import std/[strformat]
type Foo = ref object
  x, y: float

var f: Foo
new f

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

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

# When accessing values the dereference operator [] can be left out.
f.x = 13.5
echo &"{$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.

Regular Expressions #

These apply as of today (<2018-08-23 Thu>):

  • If you have PCRE library on your system, use the re stdlib.
    • PCRE syntax is used when using this library. See man pcresyntax from your terminal (online).
  • Else, use nix-regex (needs to be installed using nimble install regex).

Do not use the nre stdlib, it’s deprecated. – Ref

Using re #

import std/[re]

let
  text = """Mary had a little lamb."""
  rgx = re"(?i)mary"

echo text.replace(rgx, "Mama lamb")
Code Snippet 32: re.replace example
Mama lamb had a little lamb.

Using regex #

Below is the same example as in Code Snippet 32, including the regular expression syntax, except that import regex is done instead of import re.

import regex

let
  text = """Mary had a little lamb."""
  rgx = re"(?i)mary"

echo text.replace(rgx, "Mama lamb")
Code Snippet 33: regex.replace example
Mama lamb had a little lamb.

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 std/[os, strformat]
let
  numParams = paramCount()
  params = commandLineParams()
echo "Number of command line params: ", numParams
echo "Command line params: ", params
for n in 0 ..< numParams:
  echo &"Param {n+1} = {params[n]}"
Number of command line params: 3
Command line params: @["foo", "bar", "zoo car"]
Param 1 = foo
Param 2 = bar
Param 3 = zoo car

Use the cligen library for serious longopt and shortopt command line parameter parsing.

Terminal #

Hello World animation #

Credit
Below code is taken from this Reddit comment.

Click here to see the animation on asciinema.org.

import std/[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

Send stdout to a file #

# stdout_to_file.nim
import std/[terminal, strformat, times]

if isatty(stdout): # ./stdout_to_file
  echo "This is output to the terminal."
else:              # ./stdout_to_file | cat
  const
    logFileName = "log.txt"
  let
    # https://github.com/jasonrbriggs/nimwhistle/blob/183c19556d6f11013959d17dfafd43486e1109e5/tests/cgitests.nim#L15
    logFile = open(logFileName, fmWrite)
  stdout = logFile
  echo &"This is output to the {logFileName} file."
  echo &"- Run using nim {NimVersion} on {now()}."
Code Snippet 34: Send stdout to a file if output is not going to the terminal

Ref

Shell #

Environment Variables #

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

File paths #

Nim StdLib path #

Below code is taken from compiler/nimeval.nim in Nim source code.

import std/[os]
proc findNimStdLib*(): string =
  ## Tries to find a path to a valid "system.nim" file.
  ## Returns "" on failure.
  try:
    let nimexe = os.findExe("nim")
    if nimexe.len == 0: return ""
    result = nimexe.splitPath()[0] /../ "lib"
    if not fileExists(result / "system.nim"):
      when defined(unix):
        result = nimexe.expandSymlink.splitPath()[0] /../ "lib"
        if not fileExists(result / "system.nim"): return ""
  except OSError, ValueError:
    return ""
echo findNimStdLib()
/home/kmodi/usr_local/apps/7/nim/devel/lib

File base name #

import std/[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

Fetching various paths #

See the below example that demonstrates the difference among:

currentSourcePath.parentDir()
Path of the Nim file containing this code
getProjectPath()
Path of the Nim file being compiled
getCurrentDir()
Path where the Nim-compiled binary is run

The locations of example foo.nim and bar.nim files are as follows:

<HERE>/
 └── a/
     ├── b/
     │   └── bar.nim
     └── foo.nim

Content of a/foo.nim:

# foo.nim
import b/bar

Content of a/b/bar.nim:

# bar.nim
from std/macros import getProjectPath
from std/os import getCurrentDir, parentDir

const
  sDir = currentSourcePath.parentDir()
  # sDir will always be the path of the current bar.nim file.
  pDir = getProjectPath()
  # pDir will always be the path of the Nim file being compiled.
let
  cDir = getCurrentDir()
  # cDir will be the path where the compiled binary is being run.

echo "Source path = ", sDir
echo "Project path = ", pDir
echo "Current path = ", cDir

Table 3: Comparison of various path-retrieving functions
Current working directoryCommandsDirpDircDir
(set at compile time)(set at compile time)(set at run time)
<HERE>/nim c -r a/foo.nim<HERE>/a/b/<HERE>/a/<HERE>
<HERE>/a/./foo<HERE>/a/b/<HERE>/a/<HERE>/a/
<HERE>/a/b/../foo<HERE>/a/b/<HERE>/a/<HERE>/a/b/

References #

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 std/[os, strformat]
let
  fileName = "/tmp/foo/bar/zoo.txt"
  dataStr = "abc"
  (dir, _, _) = splitFile(fileName)
removeDir("/tmp/foo/bar/")
echo &"{dir} exists? {dirExists(dir)}"
if (not dirExists(dir)):
  echo &"  creating {dir} .."
  createDir(dir)
echo &"{dir} exists now? {dirExists(dir)}"
writeFile(fileName, dataStr)
/tmp/foo/bar exists? false
  creating /tmp/foo/bar ..
/tmp/foo/bar exists now? true

File Permissions #

Get/read file permissions #

import std/[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 std/[os]
proc parseFilePermissions(octals: string): set[FilePermission] =
  ## Converts the input permissions octal string to a Nim set for FilePermission type.
  # https://nim-lang.github.io/Nim/os.html#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 std/[random, strformat]
var rs = initRand(0xDEAD_BEEF) # some fixed seed, ref Arraymancer tests

for _ in 0 .. 10:
  let perm = &"{rs.rand(7)}{rs.rand(7)}{rs.rand(7)}"
  echo perm, " = ", parseFilePermissions(perm)
301 = {fpUserExec, fpUserWrite, fpOthersExec}
405 = {fpUserRead, fpOthersExec, fpOthersRead}
727 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupWrite, fpOthersExec, fpOthersWrite, fpOthersRead}
534 = {fpUserExec, fpUserRead, fpGroupExec, fpGroupWrite, fpOthersRead}
623 = {fpUserWrite, fpUserRead, fpGroupWrite, fpOthersExec, fpOthersWrite}
715 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupExec, fpOthersExec, fpOthersRead}
563 = {fpUserExec, fpUserRead, fpGroupWrite, fpGroupRead, fpOthersExec, fpOthersWrite}
774 = {fpUserExec, fpUserWrite, fpUserRead, fpGroupExec, fpGroupWrite, fpGroupRead, fpOthersRead}
615 = {fpUserWrite, fpUserRead, fpGroupExec, fpOthersExec, fpOthersRead}
361 = {fpUserExec, fpUserWrite, fpGroupWrite, fpGroupRead, fpOthersExec}
675 = {fpUserWrite, fpUserRead, fpGroupExec, fpGroupWrite, fpGroupRead, fpOthersExec, fpOthersRead}

Exceptions #

Exception Hierarchy #

All exceptions are objects of type Exception.

Exceptions were broken down into 2 main sub-categories: catchable errors and defects in Nim 1.4.0. For backward compatibility, defects behave like catchable errors (--panics:off). But defects are exceptions that should not be caught in your program — It’s recommended to keep them uncatchable by compiling using the --panics:on switch.

Starting Nim 1.4.0, exception types in user code should extend from either CatchableError or Defect, not from Exception directly.

  • RootObj
    • Exception
      • CatchableError
        • IOError
          • EOFError
        • OSError
        • ResourceExhaustedError
        • ValueError
          • KeyError
      • Defect (these should ideally not be caught in your program)
        • ArithmeticDefect
          • DivByZeroDefect
          • OverflowDefect
        • AccessViolationDefect
        • AssertionDefect
        • OutOfMemoryDefect
        • IndexDefect
        • FieldDefect
        • RangeDefect
        • StackOverflowDefect
        • ReraiseDefect
        • ObjectAssignmentDefect
        • ObjectConversionDefect
        • FloatingPointDefect
          • FloatInvalidOpDefect
          • FloatDivByZeroDefect
          • FloatOverflowDefect
          • FloatUnderflowDefect
          • FloatInexactDefect
        • DeadThreadDefect
        • NilAccessDefect

Custom Exceptions #

ref

type
  MyError = object of CatchableError

Raising Exceptions #

type
  MyError = object of CatchableError
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 CatchableError
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 std/[strformat]
type
  MyError = object of CatchableError
try:
  raise newException(MyError, "details about what went wrong")
except MyError:
  echo &"[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 std/[macros, sequtils, strutils, sugar]

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 std/[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 Nim Tutorial – 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 (Nim 0.19.0+) #

This is a standard Nim library.

This library was called future in Nim 0.18.0 and older.

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 35: Example of lambda in Emacs Lisp

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

import std/[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 36: 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 std/[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 Code Snippet 36 can be rewritten using -> as shown below:

import std/[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 37: Example of using proc type sugar: ->
3

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

import std/[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 std/[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

List Comprehension using [] and lc (Deprecated) #

lc has been deprecated since nim 1.2.0. New alternative is to use the sugar.collect macro, or to use the comprehension package.

List comprehension is implemented in the sugar 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:

# This code snippet will not work nim 1.2.0 onwards. See sugar.collect instead.
import std/[strformat, sugar]
let
  str = "nim lang"
  cSeq = lc[c | (c <- str),  # lc[EXPR | (ELEM1 <- LIST1),
            char]            #    SEQ_TYPE]
echo &"str of type {$type(str)} = {str}"
echo &"cSeq of type {$type(cSeq)} (seq[SEQ_TYPE]) = {cSeq}"
Code Snippet 38: Using lc to convert a string to a sequence of chars
str of type string = nim lang
cSeq of type seq[char] (seq[SEQ_TYPE]) = @['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']

The above example can be written more concisely using mapIt or map and => from sugar module as shown below or even using comp from comprehension package.

import std/[sequtils, sugar]
let
  str = "nim lang"
  cSeq = str.map(c => c)
echo cSeq
Code Snippet 39: Using map plus => sugar
@['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']

Other list comprehension examples:

# This code snippet will not work nim 1.2.0 onwards. See sugar.collect instead.
import std/[sugar]

let
  numList = 1 .. 10
  evenNums = lc[x | (x <- numList,  # lc[EXPR | (ELEM1 <- LIST1,
                     x mod 2 == 0), #            COND),
                int]                #    SEQ_TYPE]
echo evenNums
Code Snippet 40: Using lc to convert an int HSlice to a sequence of ints
@[2, 4, 6, 8, 10]

With lc now deprecated, the above example can also be written using comp from comprehension package.

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

# This code snippet will not work nim 1.2.0 onwards. See sugar.collect instead.
import std/[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
Code Snippet 41: Using lc to convert an int HSlice to a sequence of tuples
@[(a: 3, b: 4, c: 5), (a: 5, b: 12, c: 13), (a: 6, b: 8, c: 10), (a: 9, b: 12, c: 15)]

DONE comprehension #

This is an external library.

Installation #

nimble install https://github.com/alehander42/comprehension@#head

Sequence Comprehension #

As the name says, sequence comprehension will output seq objects.

Use comp[ .. : <seq element> ].

import std/[strformat]
import comprehension
let
  str = "nim lang"
  cSeq = comp[for idx, val in str: val]
echo &"str of type {$type(str)} = {str}"
echo &"cSeq of type {$type(cSeq)} = {cSeq}"
Code Snippet 42: Using comp to convert a string to a sequence of chars
str of type string = nim lang
cSeq of type seq[char] = @['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']

import std/[strformat, sequtils]
import comprehension

let
  numList = 1 .. 10
  numSeq = numList.toSeq
  evenNums = comp[for idx, val in numSeq:
                    if val mod 2 == 0:
                      val]
echo &"numList of type {$type(numList)} = {numList}"
echo &"numSeq of type {$type(numSeq)} = {numSeq}"
echo &"evenNums of type {$type(evenNums)} = {evenNums}"
Code Snippet 43: Using comp to convert an HSlice of int to a sequence of ints
numList of type HSlice[system.int, system.int] = 1 .. 10
numSeq of type seq[int] = @[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evenNums of type seq[int] = @[2, 4, 6, 8, 10]

import std/[strformat, tables]
import comprehension

let
  myTable = {0:0, 3:1, 4:5}.toTable()
  mySeq = comp[for key, val in myTable: key + val]

echo &"myTable of type {$type(myTable)} = {myTable}"
echo &"mySeq of type {$type(mySeq)} = {mySeq}"
Code Snippet 44: Using comp to get a sequence from a table
myTable of type Table[system.int, system.int] = {0: 0, 3: 1, 4: 5}
mySeq of type seq[int] = @[0, 4, 9]

Set Comprehension #

As the name says, set comprehension will output sets (HashSet objects).

Use comp{ .. : <set element> }.

It is necessary to import std/[sets] too, when doing set comprehensions.

import std/[strformat, sets]
import comprehension
let
  str = "nim lang"
  cSet = comp{for idx, val in str: val}
echo &"str of type {$type(str)} = {str}"
echo &"cSet of type {$type(cSet)} = {cSet}"
Code Snippet 45: Using comp to convert a string to a set of chars
str of type string = nim lang
cSet of type HashSet[system.char] = {'g', ' ', 'l', 'n', 'i', 'a', 'm'}

import std/[strformat, sets]
import comprehension

let
  mySeq = @[1, 2, 3, 4, 5, 100]
  mySet = comp{for key, val in mySeq: val*val}

echo &"mySeq of type {$type(mySeq)} = {mySeq}"
echo &"mySet of type {$type(mySet)} = {mySet}"
Code Snippet 46: Using comp to get a set from a sequence
mySeq of type seq[int] = @[1, 2, 3, 4, 5, 100]
mySet of type HashSet[system.int] = {4, 16, 1, 9, 25, 10000}

As it can be seen from these examples, do not expect the outputs of set comprehension to have elements in any particular order.

import std/[strformat, sets, tables]
import comprehension

let
  myTable = {1:100, 2:200, 3:300}.toTable()
  mySet = comp{for key, val in myTable: val*val}

echo &"myTable of type {$type(myTable)} = {myTable}"
echo &"mySet of type {$type(mySet)} = {mySet}"
Code Snippet 47: Using comp to get a set from a table
myTable of type Table[system.int, system.int] = {3: 300, 2: 200, 1: 100}
mySet of type HashSet[system.int] = {90000, 40000, 10000}

Table Comprehension #

As the name says, table comprehension will output Table objects.

Use comp{ .. : <table element> }.

It is necessary to import std/[tables] too, when doing table comprehensions.

import std/[strformat, tables]
import comprehension
let
  str = "nim lang"
  cTable = comp{for idx, val in str: {idx: val}}
echo &"str of type {$type(str)} = {str}"
echo &"cTable of type {$type(cTable)} = {cTable}"
Code Snippet 48: Using comp to convert a string to a table of chars
str of type string = nim lang
cTable of type Table[system.int, system.char] = {4: 'l', 5: 'a', 7: 'g', 3: ' ', 2: 'm', 6: 'n', 0: 'n', 1: 'i'}

import std/[strformat, tables]
import comprehension

let
  myTable1 = {0:0, 3:1, 4:5}.toTable()
  myTable2 = comp{for key, val in myTable1: {key : key + val}}

echo &"myTable1 of type {$type(myTable1)} = {myTable1}"
echo &"myTable2 of type {$type(myTable2)} = {myTable2}"
Code Snippet 49: Using comp to get a table from a table
myTable1 of type Table[system.int, system.int] = {0: 0, 3: 1, 4: 5}
myTable2 of type Table[system.int, system.int] = {4: 9, 3: 4, 0: 0}

As it can be seen from these examples, do not expect the outputs of table comprehension to have elements in any particular order.

import std/[strformat, tables]
import comprehension

let
  mySeq = @[1, 2, 3]
  myTable = comp{for key, val in mySeq: {key : val*val}}

echo &"mySeq of type {$type(mySeq)} = {mySeq}"
echo &"myTable of type {$type(myTable)} = {myTable}"
Code Snippet 50: Using comp to get a table from a sequence
mySeq of type seq[int] = @[1, 2, 3]
myTable of type Table[system.int, system.int] = {2: 9, 0: 1, 1: 4}

Below example originated from a question on Nim Gitter.

import std/[strutils, strformat, tables]
import comprehension

let
  myStr = "abc=def ghi=jkl"
  mySeq = myStr.split(' ')
  myTable = comp{for _, val in mySeq: {val.split('=')[0] : val.split('=')[1]}}

echo &"myStr of type {$type(myStr)} = {myStr}"
echo &"mySeq of type {$type(mySeq)} = {mySeq}"
echo &"myTable of type {$type(myTable)} = {myTable}"
Code Snippet 51: Using comp to get a table from a string
myStr of type string = abc=def ghi=jkl
mySeq of type seq[string] = @["abc=def", "ghi=jkl"]
myTable of type Table[system.string, system.string] = {"ghi": "jkl", "abc": "def"}

Here’s another way to do the same using String Tables (credit @awr1 from GitHub from here):

import std/[strtabs, strutils, strformat]

let
  myStr = "abc=def ghi=jkl"
  mySeq = myStr.split({'=', ' '})
  myStrTable = mySeq.newStringTable(modeCaseSensitive)

echo &"myStr of type {$type(myStr)} = {myStr}"
echo &"mySeq of type {$type(mySeq)} = {mySeq}"
echo &"myStrTable of type {$type(myStrTable)} = {myStrTable}"
Code Snippet 52: Using strtabs to get a string table from a string
myStr of type string = abc=def ghi=jkl
mySeq of type seq[string] = @["abc", "def", "ghi", "jkl"]
myStrTable of type StringTableRef = {ghi: jkl, abc: def}

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"
""").getTable()
  pkg = "tmux"
for key, val in pairs(cfg[pkg].getTable()):
  echo key
req_env_vars
set_env_vars

strfmt (Yep, this is different from strformat#

My Fork
Source | Documentation
  • To install: nimble install https://github.com/kaushalmodi/strfmt

The original repo named strfmt by a bitbucket.org user lyro does not exist any more (as of <2021-12-17 Fri>).

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

Unfortunately though, the strfmt developer has stopped using Nim and has also removed their repo from bitbucket.org. 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 https://github.com/kaushalmodi/bignum

bignum Example #

import std/[strformat]
import 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 &"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 std/[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

As of 0831292863, importing typetraits is no longer required if you just need to print a type name. Example: echo $type(100) or echo $(100).type will print "int".

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

Nim Issue #7975

Using $ works #
import std/[strformat, typetraits]
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo &"Tuple person of type {$type(person)} = {person}"
Tuple person of type Person = (name: "Peter", age: 30)
Using name(VAR.type) works too #
import std/[strformat, typetraits]
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo &"Tuple person of type {name(person.type)} = {person}"
Tuple person of type Person = (name: "Peter", age: 30)
Using VAR.type.name FAILS #
import std/[strformat, typetraits]
type Person = tuple[name: string, age: int]
let person: Person = ("Peter", 30)
echo &"Tuple person of type {person.type.name} = {person}"
Hint: typetraits [Processing]
nim_src_QKIsPQ.nim(7, 9) template/generic instantiation of `fmt` from here
/home/kmodi/stow/pkgs/nim/devel/lib/pure/strformat.nim(260, 8) Error: type mismatch: got <string, type string>
but expected one of:
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(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: cstring)
  first type mismatch at position: 2
  required type: cstring
  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: char)
  first type mismatch at position: 2
  required type: char
  but expression 'type(person).name' is of type: type string
2 other mismatching symbols have been  suppressed; compile with --showAllMismatches:on to see them

expression: add(fmtRes446027, 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 std/[strformat, typetraits]
type Person = tuple[namex: string, age: int]
let person: Person = ("Peter", 30)
echo &"Tuple person of type {person.type.name} = {person}"
Tuple person of type Person = (namex: "Peter", age: 30)

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

Nim Issue #7976

The results of the below code blocks are frozen in the incorrect output state when this bug existed. It’s not known which commit on Nim devel fixed this bug. But as of a1e268e3dccdde4df9b11a0ee87971e1143fbb43 on <2019-01-02 Wed>, this issue is verified as fixed.

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 std/[strformat]
type Person = tuple[name: string, age: int]
let
  person1: Person = ("Peter", 30)
  person2: tuple[name: string, age: int] = (name: "Peter", age: 30)
echo &"Tuple person1 of type {$person1.type} = {person1}"
echo &"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 std/[strformat]
type Person = tuple[name: string, age: int]
let
  person1: Person = ("Peter", 30)
  person2: tuple[name: string, age: int] = (name: "Peter", age: 30)
echo &"Tuple person2 of type {$person2.type} = {person2}"
echo &"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 std/[tables]

var a = {"hi": 1, "there": 2}.toTable
echo a["hi"], " ", a.len
echo "a is of type ", $type(a)
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 std/[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
spam: 1
foo: 3
iterate keys:
eggs
spam
foo
iterate values:
2
1
3
hash["spam"] = 1
hash["key_not_set_yet"] is not yet set.

Nested Tables #

import std/[tables, strformat]

type
  Coord = tuple
    x: int
    y: int
  CoordTable = Table[Coord, int]

var
  t: CoordTable

let
  coords = @[(1,1).Coord, (2,2), (7,8), (8,8), (2,6), (0,4), (2,2), (7,8), (7,8)]

for coord in coords:
  if t.hasKey(coord):
    t[coord].inc
  else:
    t[coord] = 1

echo t
Code Snippet 53: Tuple indexed table/map/dictionary
{(x: 2, y: 2): 2, (x: 7, y: 8): 3, (x: 8, y: 8): 1, (x: 2, y: 6): 1, (x: 1, y: 1): 1, (x: 0, y: 4): 1}

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 std/[strformat, critbits]
var
  s: CritBitTree[void]
  t: CritBitTree[int]
echo &"{$type(s)} s (CBT as set) = {s}"
echo &"{$type(t)} 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 std/[strformat, critbits]
var t: CritBitTree[int]
t["a"] = 1
echo &"{$type(t)} t = {t}"
echo """Mutating or changing the value of t["a"] .. """
t["a"] = 2
echo &"{$type(t)} 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 std/[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 std/[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 std/[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: text {"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 std/[strformat, critbits]
var s: CritBitTree[void]
echo &"Type of s: {$type(s)}"
echo """Adding "a" .."""
incl(s, "a")
echo """Adding "b" .."""
s.incl("b")
echo &"  s has {s.len} elements: {s}"
echo """Adding "a" again .."""
s.incl("a")
echo &"  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 std/[strformat, critbits]
var
  s: CritBitTree[void]
  t: CritBitTree[int]
s.incl("y")
t["a"] = 1
echo &"{$type(s)} s = {s}"
echo &"""s["x"] exists? {s.contains("x")}"""
echo &"""s["y"] exists? {s.hasKey("y")}"""
echo &"{$type(t)} t = {t}"
echo &"""t["a"] exists? {t.contains("a")}"""
echo &"""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 std/[strformat, critbits]
var
  s: CritBitTree[void]
  t: CritBitTree[string]
s.incl("foo")
s.incl("bar")
s.incl("zoo")
echo &"{$type(s)} s = {s}"
echo &"Number of elements in s = {s.len}"
t["a"] = "hello"
t["b"] = "world"
echo &"{$type(t)} t = {t}"
echo &"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 std/[strformat, critbits]
var t: CritBitTree[int]
t["a"] = 10
echo &"{$type(t)} t = {t}"
inc(t, "a")
echo &"{$type(t)} t = {t}"
t.inc("a")
echo &"{$type(t)} 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 std/[strformat, critbits]
var t: CritBitTree[int]
echo &"{$type(t)} t = {t}"
echo """Initializing/incrementing "a" key to 1 .."""
t.inc("a")
echo &"  {$type(t)} t = {t}"
echo """Decrementing "a" key by 1 .."""
t.inc("a", -1)
echo &"  {$type(t)} t = {t}"
echo """Incrementing "a" key by 4 .."""
t.inc("a")
t.inc("a")
t.inc("a")
t.inc("a")
echo &"  {$type(t)} t = {t}"
echo """Decrementing "a" key by 2 .."""
t.inc("a", -1)
t.inc("a", -1)
echo &"  {$type(t)} 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 std/[strformat, critbits]
var t: CritBitTree[int]
echo &"{$type(t)} t = {t}"
t.inc("a", 5)
t.inc("b", 7)
echo &"{$type(t)} t = {t}"
t.inc("a", -10)
t.inc("b", -100)
echo &"{$type(t)} 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 std/[strformat, critbits]
var t: CritBitTree[int]
t["a"] = 1
echo &"""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 std/[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 sequtils #

This is a standard Nim library.

mapIt #

mapIt is a convenience template around the map proc to reduce typing.

The template injects the it variable which you can use directly in an expression.

import std/[sequtils]
let
  nums = @[1, 2, 3, 4]
  strings = nums.mapIt($(4 * it))
echo strings
@["4", "8", "12", "16"]

Below shows the how the map plus => can be rewritten a bit more concisely using mapIt (Thanks to the SO user Peheje for this tip):

import std/[sequtils]
let
  str = "nim lang"
  cSeq = str.mapIt(it)
echo cSeq
@['n', 'i', 'm', ' ', 'l', 'a', 'n', 'g']

DONE options #

This is a standard Nim library.

Declaring Option variables #

import std/[options]

var s: Option[string]
echo s
none(string)

An unset Option variable defaults to None[TYPE].

Setting Option variables #

import std/[options]

var s: Option[float]

s = some(123.0)
echo s

# s = some(1)
# Above will give this error:
#   Error: type mismatch: got <Option[system.int]> but expected 'Option[system.float]'
# So specify the type explicitly.
s = some[float](1)
echo s

s = none(float)
echo s
s = none[float]() # This is the same as none(float) used above.
echo s
some(123.0)
some(1.0)
none(float)
none(float)

Getting Option variable values #

import std/[options]

var s: Option[int]
echo s
s = some(456)
echo s
echo s.get()
none(int)
some(456)
456

Checking if Option is None/Some #

import std/[options, strformat]

var s: Option[string]
echo s
echo &"Is s None? {s.isNone()}, is s Some? {s.isSome()}"

s = some("")
echo s
echo &"Is s None? {s.isNone()}, is s Some? {s.isSome()}"

s = some("abc")
echo s
echo &"Is s None? {s.isNone()}, is s Some? {s.isSome()}"
none(string)
Is s None? true, is s Some? false
some("")
Is s None? false, is s Some? true
some("abc")
Is s None? false, is s Some? true

Option[string] as a solution to “nil” string #

In Nim 0.19.0+, string type vars cannot be set to nil. Earlier a string was set to nil to distinguish between “string not set” and “string set to an empty string”.

So to solve that problem, one possible solution is to use the Option[string] type.

  • An unset Option[string] would be None[string].
  • An Option[string] set to an empty string (some("")) would be Some("").
import std/[options]

var s: Option[string]
echo s
s = some("")
echo s
s = some("abc")
echo s
for c in get(s): # Get the value of type T from an Option[T] type var
  echo c
none(string)
some("")
some("abc")
a
b
c
FIXED $ for Option[string] should wrap the output in double quotes #

Nim Issue #8658

TODO json #

This is a standard Nim library.

Looping through keys in JSON #

Ref

import std/[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"]

Getting JSON via an API endpoint #

  • When using the httpclient module to get content from an https: domain, compile the code with -d:ssl switch.
import std/[httpclient, json]

var client = newHttpClient()
let jObj = client.getContent("https://scripter.co/jf2feed.json").parseJson()
echo jObj["author"]
echo jObj["author"].pretty()
echo jObj["author"]["name"]
{"name":"Kaushal Modi","type":"card","url":"https://scripter.co/"}
{
  "name": "Kaushal Modi",
  "type": "card",
  "url": "https://scripter.co/"
}
"Kaushal Modi"

ref

Empty JSON #

import std/[json, strformat]
let
  emptyJArray = "[]".parseJson
echo &"emptyJArray.kind = {emptyJArray.kind}"
echo &"emptyJArray = {emptyJArray}"
echo &"emptyJArray.len = {emptyJArray.len}"
Code Snippet 54: Empty JSON Array
emptyJArray.kind = JArray
emptyJArray = []
emptyJArray.len = 0

Loggers #

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

echo "x is ", $type(x)
echo "y is ", $type(y)
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 #

Nim Tutorial – Type Conversion

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

import std/[strformat]

var
  x = 1.0
  xType = $type(x)
echo &"x is {xType} and its value is {x}."

var
  xCasted = x.int # int(x) will also work
  xCastedType = $type(xCasted)
echo &"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 strformat
    var x = int(1.0 / 3) # type conversion
    echo &"x is {$type(x)} 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 strformat
    var y: seq[int] = @[] # empty seq needs type specification
    echo &"y is {$type(y)} 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)
    
    ptr 0x7f94ee9ac058 --> ['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 std/[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.

  • 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
    

FIXED Incorrect overflow/underflow error #

These issues got fixed in Nim PR #11652, thanks to @krux02 from GitHub.


Below snippet used to work in Nim 0.19.0 and older, but started failing with below error in Nim 0.20.0 and newer – <2019-06-20 Thu>.

Even though I understand that Nim 0.20.0 has overflow checked enabled by default, it doesn’t make sense why a low(int) and high(int) value should trigger that error.

proc positiveOrNegative(num: int): string =
  result = case num
  of low(int) .. -1:
    "negative"
  of 0:
    "zero"
  of 1 .. high(int):
    "positive"
echo positiveOrNegative(-1)
echo positiveOrNegative(10000000)
echo positiveOrNegative(0)
negative
positive
zero

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
    

“Else If” inside Case Expression #

Case Expression has an interesting example of use of elif as one of the case statement clauses. Below code is an adaption of that snippet.

import std/[strutils]
proc likes(animal: string): string =
  let
    food = case animal
      of "dog": "bones"
      of "cat": "mice"
      elif animal.endsWith"whale": "plankton"
      else: "ice cream"
  result = animal & " likes " & food

echo "dog".likes
echo "cat".likes
echo "blue whale".likes
echo "sperm whale".likes
echo "me".likes
Code Snippet 55: Use of elif in a case statement
dog likes bones
cat likes mice
blue whale likes plankton
sperm whale likes plankton
me likes ice cream

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

let
  foo = AlphaRange(low: 7, high: 11)

# Define single value yielding iterator for type AlphaRange
iterator items(ar: AlphaRange): int =
  var i = ar.low
  while i <= ar.high:
    yield i
    inc i

for n in foo: # uses AlphaRange.items
  echo n

# Define pair value yielding iterator for type AlphaRange
iterator pairs(ar: AlphaRange): (int, char) =
  for i in ar:  # uses AlphaRange.items
    yield (i, char(i + ord('a') - 1))

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

A pairs iterator returns a 2-field tuple.

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.

This example works until Nim stable v1.6.6 but is broken on Nim v1.7.1 (63cca93ea9) – gives the error Error: identifier expected, but got ‘8’. See Nim Issue #19806.

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
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 56: 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 Code Snippet 56, 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 std/[strformat]
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 &"countTo(10) is of type `{$type(countTo(10))}'"
Code Snippet 57: 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 / Method Call 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

UFCS is documented in the Nim Manual as Method Call Syntax.

Command Invocation Syntax #

The Nim Command Invocation Syntax allows routines like procs to be invoked without the ().

This command invocation syntax also works for expressions, but then only a single argument may follow. This restriction means echo f 1, f 2 is parsed as echo(f(1), f(2)) and not as echo(f(1, f(2))).

proc oneMandArg(x: int, y: int = 0): int = x + y
proc singleArg(x: int): int = 20*x

echo oneMandArg 1, " ", singleArg 2  # prints "1 40"
1 40

Based on how the command invocation syntax was used in the above example when calling oneMandArg, that example runs fine — The compiler saw that the first argument to oneMandArg 1 (an int) was valid, but the second argument (optional) was expected to be an int too, but instead it sees " " (a string). So the compiler silently uses the default value 0 of the optional second argument, and passes the " " to echo.

But if you try to run the below, you get a cryptic Error: invalid indentation:

proc oneMandArg(x: int, y: int = 0): int = x + y
let fail = oneMandArg 1, oneMandArg 8   # Wrong. Too many arguments for a command call

To fix that, the method call syntax (UFCS) may be used to provide one more argument in this case:

proc oneMandArg(x: int, y: int = 0): int = x + y
let x = oneMandArg(1, oneMandArg 8)  # traditional procedure call with 2 arguments
let y = 1.oneMandArg oneMandArg 8    # same thing as above
echo x, " ", y
9 9

Above, method call syntax (UFCS) + command invocation syntax is used for the first invocation of oneMandArg, and only command invocation syntax is used for the second.

The command invocation syntax also cannot have complex expressions as arguments. For example: (anonymous procs), if, case or try. The (do notation) is limited, but usable for a single proc (see the example that section).

Function calls with no arguments still need () to distinguish between a call and the function itself as a first class value.

import std/[strformat]
proc foo(): string = "hello"
echo &"`foo' is of type `{$type(foo)}'"
echo &"`foo()` (return value of that) is of type `{$type(foo())}'"
`foo' is of type `proc (): string{.noSideEffect, gcsafe, locks: 0.}'
`foo()` (return value of that) is of type `string'

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
Code Snippet 58: Proc with no side effect

But below will fail:

proc minus(x, y: int): int {. noSideEffect .} =
  echo x, " ", y
  x - y
discard minus(4, 2)
Code Snippet 59: Cannot use echo in proc with no side effect

with the error:

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

The func keyword is a shortcut for a proc with {. noSideEffect .}.

So Code Snippet 58 can be rewritten as:

func sum(x, y: int): int =
  x + y
Code Snippet 60: Func implies a proc with no side effect

Similarly, rewriting Code Snippet 59 with func will give the same error:

func minus(x, y: int): int =
  echo x, " ", y
  x - y
discard minus(4, 2)
Code Snippet 61: Cannot use echo in func
No side effect debug #

We see in Code Snippet 59 and Code Snippet 61 that we cannot use echo as side effects are not allowed in those.

But there is a way to still use echo statements in no-side-effect procs (aka funcs) – debugEcho.

Same as echo, but as a special semantic rule, debugEcho pretends to be free of side effects, so that it can be used for debugging routines marked as noSideEffect.

So by simply replacing echo with debugEcho, Code Snippet 61 will compile (and so will Code Snippet 59):

func minus(x, y: int): int =
  debugEcho x, " ", y
  x - y
discard minus(4, 2)
Code Snippet 62: Using debugEcho in func
4 2

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

Forward Declaration #

To forward-declare a Nim proc, simply type the proc signature without the = at the end.

proc foo(x: string): int =
  x.len
echo foo("abc")
Code Snippet 63: A Nim proc defined and executed in order
3

Below code snippet is the same as above, but with forward declaration.

proc foo(x: string): int # forward declaration
echo foo("abc")
proc foo(x: string): int =
  x.len
Code Snippet 64: A Nim proc first forward-declared, then executed, and finally defined
3

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 std/[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 std/[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 UFCS (Method Call Syntax): arg1.procname(arg2)
echo powersOfTwo.filter(proc (x: int): bool = x > 4)
Code Snippet 65: Anonymous Procedures
@[8, 16, 32]
@[8, 16, 32]

Also see Lambdas (=>).

Anonymous procs in Generics #

If you need to define an anon proc taking a generic type (typically represented using T in the signature), use auto for type specification in the anon proc definition.

Thanks to @zah from GitHub for this tip. – ref

proc foo[T](input: T; op: proc(x: T): string): string =
  op(input)
echo foo(100, proc (x: auto): string = $x) # Use `auto` where there was `T` in the signature
echo foo("abc", proc (x: auto): string = $x)
Code Snippet 66: Anonymous procedure definition for a Generic type
100
abc
Do notation #

Experimental feature

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 Code Snippet 65 using the do notation:

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

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

With a proc have a signature like proc foo[T](op: proc (x: T): string): string =, here’s now an example anon proc would look in the anon proc style vs do notation:

Anon Procs
foo(100, proc (x: auto): string = $x) or 100.foo(proc (x: auto): string = $x)
Do Notation
foo(100) do (x: auto) -> string: $x or 100.foo() do (x: auto) -> string: $x. Notice how the foo call looks as if it has only one parameter.

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 std/[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 std/[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 std/[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 std/[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 (optional parentheses).

import std/[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
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"]
@["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 std/[strformat]
let b = 3
echo &"Outside block: b is of type {$type(b)} of value {b}."
block:                          # one block
  let b = "4"                   # shadowing is probably a dumb idea
  echo &"  Inside an unlabeled block: b is of type {$type(b)} of value {b}."
block:                          # another block
  let b = 1.234                 # shadowing is probably a dumb idea
  echo &"  Inside another unlabeled block: b is of type {$type(b)} of value {b}."
block foo:                      # yet another block, this one labeled
  let b = [1, 2, 3, 4]          # shadowing is probably a dumb idea
  echo &"  Inside 'foo' block: b is of type {$type(b)} of value {b}."
echo &"Outside block: Once again, b is of type {$type(b)} of value {b}."
Outside block: b is of type int of value 3.
  Inside an unlabeled block: b is of type string of value 4.
  Inside another unlabeled block: b is of type float64 of value 1.234.
  Inside 'foo' block: b is of type array[0..3, int] of value [1, 2, 3, 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 std/[strformat]
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 &"Value of {$type(a)} a is {a}"
echo &"Value of {$type(b)} b is {b}"
echo &"Value of {$type(c)} c is {c}"
echo &"Value of {$type(d)} d is {d}"
echo &"Value of {$type(e)} 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 67: 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 68: 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 67, 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 68, 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 std/[strformat]
let
  a: int = 0b1011_1010
  b: int = 0b0101_0011
echo &"{a:#010b} ^  {b:#010b} = {(a xor b):#010b}"
echo &"{a:#010b} &  {b:#010b} = {(a and b):#010b}"
echo &"{a:#010b} |  {b:#010b} = {(a or b):#010b}"
echo &"{a:#010b} >> 3          = {(a shr 3):#b}"
echo &"{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: "", species: "", 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:

import std/[strformat]
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.repr} (type {$type(spot)})"
echo &"spot[] = {spot[]} (type {$type(spot[])})"
spot[].sleep()
echo spot[]
spot = ref 0x7f12f8735050 --> [name = 0x7f12f8735090"Spot",
species = 0x7f12f87350c0"C. lupus",
age = 1] (type AnimalRef)
spot[] = (name: "Spot", species: "C. lupus", age: 1) (type Animal)
(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 # enum with holes
    sigQuit = 3, # these commas are optional
    sigAbort = 6,
    sigKill = 9

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

var
  dir: CompassDirections = cdSouth
  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 69: Enum Types (normal, enums with holes, enums with stringified values)
cdSouth
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 std/[strformat]
var
  someOrdinal = 'A'                 # char is an ordinal
echo &"someOrdinal ({$type(someOrdinal)}) = {someOrdinal}"

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

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

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

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

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

echo &"value of someOrdinal: {someOrdinal}"
echo &"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 std/[strformat]
type
  CompassDirections = enum
    cdNorth, cdEast, cdSouth, cdWest

for direction in ord(low(CompassDirections)) .. ord(high(CompassDirections)):
  echo &"{CompassDirections(direction)} ({$type(CompassDirections(direction))}), " &
       &"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 Code Snippet 69 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.

FIXED Ordinal methods on enums with holes #

Thanks to @LemonBoy from GitHub for fixing this in Nim PR #8264.

Now when trying to run the below snippet:

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

var nonOrdinal = sigQuit
inc nonOrdinal

will give this error:

nim_src_xNvIoW.nim(9, 5) Error: type mismatch: got <Signals>
but expected one of:
proc inc[T: Ordinal | uint | uint64](x: var T; y = 1)

expression: inc nonOrdinal
Older issue
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 " ", nonOrdinal
echo "inc on holey enums"
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
 sigQuit
inc on holey enums
 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

borrow pragma #

borrow for procs #

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.

import std/[strformat]

type
  Dollars = distinct float

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

var a = Dollars(25.0)
echo &"a of type {$type(a)} = {a}"
a = 10.Dollars * (20.Dollars + 1.Dollars)
echo &"a of type {$type(a)} = {a}"
a of type Dollars = 25.0
a of type Dollars = 210.0

The above example failed to compile in Nim 0.19.0, but was soon fixed in 959e3a08 by @LemonBoy from GitHub.

borrow for object fields #

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
value.a = 100
echo value.a
100

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"
.".
\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.

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.

DONE Bitsets (Set type) #

Nim Manual – Set type

Nim comes with a built-in way to build a set of ordinal types.

In order for a type to be usable in a bitset, it must be an ordinal and high(T) < 216. For sets of non-ordinal types, see the sets module, which contains hashsets.

However, the best practice is to keep bitset size as small as possible since each possible element in the set consumes one bit, therefore a bitset of 216 elements will consume 64KiB.

Bitsets have all the useful operations of mathematical sets:

Table 4: Set type operations
SyntaxDescriptionExample Code
A + Bunion of sets A and B{'a' .. 'm'} + {'n' .. 'z'} == {'a' .. 'z'}
A * Bintersection of sets A and B{'a' .. 'm'} * {'c' .. 'z'} == {'c' .. 'm'}
A - Bdifference of two sets (A with B’s elements){'a' .. 'z'} - {'b' .. 'd'} == {'a', 'e' .. 'z'}
A == Bare both sets A and B equal?{'a' .. 'c'} == {'a' .. 'c'}
A <= Bis set A a subset of set B, or are both equal ?{'a' .. 'c'} <= {'a' .. 'z'}
A < Bis set A a strict subset of set B (i.e. A != B)?{'b' .. 'c'} < {'a' .. 'z'}
e in Ais e an element of set A?'d' in {'a' .. 'z'}
contains(A, e)same as abovecontains({'a' .. 'z'}, 'd')
card(A)the cardinality of A (number of elements in A)card({'a' .. 'z'})
incl(A, e)same as A = A + {e}, but when using incl, A has to be a varvar foo = {'b' .. 'z'}; incl(foo, 'a'); foo == {'a' .. 'z'}
excl(A, e)same as A = A - {e}, but when using excl, A has to be a varvar foo = {'a' .. 'z'}; excl(foo, 'a'); foo == {'b' .. 'z'}
echo "Union: ", {'a' .. 'd'} + {'e' .. 'h'}
echo "Intersection: ", {'a' .. 'd'} * {'c' .. 'z'}
echo "Difference: ", {'a' .. 'h'} - {'b' .. 'd'}
Union: {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}
Intersection: {'c', 'd'}
Difference: {'a', 'e', 'f', 'g', 'h'}
echo "Equal: ", {'a' .. 'c'} == {'a' .. 'c'}, " ", {'a' .. 'c'} == {'a' .. 'b'}
echo "Subset or Equal: ", {'a' .. 'c'} <= {'a' .. 'c'}, " ", {'a' .. 'b'} <= {'a' .. 'c'}, " ", {'a' .. 'd'} <= {'a' .. 'c'}
echo "Subset: ", {'a' .. 'c'} < {'a' .. 'c'}, " ", {'a' .. 'b'} < {'a' .. 'c'}, " ", {'a' .. 'd'} < {'a' .. 'c'}
Equal: true false
Subset or Equal: true true false
Subset: false true false
echo "in operator: ", 'd' in {'a' .. 'z'}, " ", '1' in {'a' .. 'z'}
echo "contains: ", contains({'a' .. 'z'}, 'd'), " ", contains({'a' .. 'z'}, '1')
echo "card: ", card({'a' .. 'z'})
var foo = {'b' .. 'd'}
echo "Before incl 'a': ", foo
incl(foo, 'a')
echo "After incl 'a': ", foo
echo "Before excl 'a': ", foo
excl(foo, 'a')
echo "After excl 'a': ", foo
in operator: true false
contains: true false
card: 26
Before incl 'a': {'b', 'c', 'd'}
After incl 'a': {'a', 'b', 'c', 'd'}
Before excl 'a': {'a', 'b', 'c', 'd'}
After excl 'a': {'b', 'c', 'd'}

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

in is a binary operator.

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#

notin is a binary operator.

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

DONE Files #

Reading from a File #

Suppose we have a file nim_files/kittens.txt relative to the current directory with the following contents:

Spitfire
Vivian
Motor

We can use the readFile proc to read the entire file into memory:

let
  # This will read the entire file into the string entireFile
  entireFile = readFile("nim_files/kittens.txt")
echo entireFile  # prints the entire file
Code Snippet 70: Read a file
Spitfire
Vivian
Motor

We can also read the lines of a file by opening a File object and using the readLine proc to read individual lines.

let
  f = open("nim_files/kittens.txt")
  firstLine = f.readLine()
echo firstLine  # prints Spitfire
# Close the file object when you are done with it.
f.close()
Code Snippet 71: File open + read a line
Spitfire

Writing to a File #

We can write a string to a file using the writeFile proc.

let
  fileName = "nim_files/cats.txt"
  text = "Cats are very cool!"
writeFile(fileName, text)

# Now read back that written file.
echo readFile(fileName)
Code Snippet 72: Write a file
Cats are very cool!

This will create a file on the system named nim_files/cats.txt containing “Cats are very cool!”.

We can also write a file line by line using a File object and the writeLine proc.

let
  fileName = "nim_files/cat_activities.txt"
  # The fmWrite constant specifies that we are opening the file for writing.
  f = open(fileName, fmWrite)
  lines_text = ["Play", "Eat", "Sleep"]

for line in lines_text:
  f.writeLine(line)

f.close()

# Now read back that written file.
echo readFile(fileName)
Code Snippet 73: File open + write lines
Play
Eat
Sleep

TODO JSON #

DONE Varargs #

Nim Manual – Varargs

Standard varargs simply allows you to pass multiple parameters to your function.

proc printThings(things: varargs[string]) =
  for thing in things:
    echo thing

printThings "words", "to", "print"
words
to
print

The compiler converts the list of arguments to an array implicitly.

This transformation is only done if the varargs parameter is the last parameter in the procedure header.

However, trying to run:

printThings 1, "string", @[1, 2, 3]

will fail to compile because the compiler won’t coerce the non-string arguments 1 and @[1, 2, 3] into strings, and the printThings proc needs the varargs to be strings.

Luckily enough, there is a tool to fix this; it is also possible to perform type conversions in this context:

proc printThings(things: varargs[string, `$`]) =
  for thing in things:
    echo thing

printThings "thing 1", 2, @[4, 5, 6]
thing 1
2
@[4, 5, 6]

In this example $ is applied to any argument that is passed to the parameter things. (Note that $ applied to strings is a nop.)

Note that an explicit array constructor passed to a varargs parameter is not wrapped in another implicit array construction:

import std/[strformat]
proc takeV[T](a: varargs[T]) =
  echo &"a (type: {$type(a)}) = {a}"
  for elem in a:
    echo elem
takeV([123, 2, 1])
a (type: varargs[int]) = [123, 2, 1]
123
2
1

Varargs Typed #

varargs[typed] is treated specially — It matches a variable list of arguments of arbitrary type but always constructs an implicit array.

This is required so that the builtin echo proc does what is expected. (The signature of echo is proc echo*(x: varargs[typed, `$`]) {...}).

So echo @[1, 2, 3] prints "@[1, 2, 3]" and not "123".

TODO Object Oriented Programming #

TODO Macros #

Nim’s syntax is incredibly versatile, and macros can be used to rewrite the abstract syntax tree (AST) of a program. The general process for writing a macro consists of two steps that are repeated until the desired results are achieved:

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 std/[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 std/[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 std/[parseutils]
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 std/[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 std/[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 std/[unittest]

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

  test "failing test to be ignored":
    check:
      true == false
    skip

[Suite] something
  [OK] passing test
    /tmp/babel-sQr7ZH/nim_src_WMFvaB.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 std/[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.

One Liners #

As Nim syntax is indentation dependent, one-lining any arbitrary Nim code might not be possible. But it certainly is, for many Nim constructs.

One-lining Nim code should be possible in general if the below rules are followed:

  1. Replace newlines after each top-level indentation Nim code with ; (see the below example to understand this better).
  2. Do not use ; to end the one-liner.
  3. You can define only one proc in a Nim one-liner. And if you need to define that one proc, you need to forward declare it, and then define the proc at the very end.
  4. Wrap if .. else, for, etc. constructs in parentheses.

Let’s take the below example that you’d like to one-line.

proc foo(x: string): int =
  if x == "":
    -1
  else:
<