Nim is a relatively new programming language which allows users to write easy-to-read high-performance code. But if you are reading this Nim tutorial, the chances are that you already know about Nim.

The tutorial is available both online and as a PDF.

This is a work-in-progress — if you spot any errors and/or you have an idea how to make this tutorial better, please report it to the issue tracker.

Who is this for?

  • People with minimal previous programming experience

  • People with some programming experience in other programming languages

  • People who want to explore Nim for the first time, starting from scratch

Who is this not for?

  • People with lots of programming experience — other, more advanced, tutorials might suit you better. See Official Tutorial or Nim by Example.

  • People experienced in Nim (feel free to help make this tutorial better)

How to use this tutorial?

The aim of this tutorial is to give you the basics of programming and the Nim syntax so you can have an easier time following other tutorials and/or explore further by yourself.

Instead of just reading what is written, it would be the best if you try the stuff by yourself, modify the examples, think of some examples of your own, and be curious in general. The exercises at the end of some chapters should be non-negotiable — don’t skip them.

If you need additional help understanding some parts of the tutorial or with the exercises, you can always ask for help on the Nim forum, the Nim Gitter channel, their Discord server, or Nim’s IRC channel on freenode, #nim.


Installation

Installing Nim

Nim has ready made distributions for all three major operating systems and there are several options when it comes to installing Nim.

You can follow the official installation procedure to install the latest stable version, or you can use a tool called choosenim which enables you to easily switch between the stable and the latest development version if you’re interested in the latest features and bugfixes.

Whichever way you choose, just follow the installation procedure explained at each link and Nim should be installed. We will check that the installation went well in a coming chapter.

If you’re using Linux, there is a high probability that your distribution has Nim in the package manager. If you are installing it that way, make sure it’s the most recent version (see the website for what is the latest version), otherwise install via one of two methods mentioned above.

In this tutorial we will use the stable version, which is Nim 0.18.0 at the time of writing.

Installing additional tools

You can write Nim code in any text editor, and then compile and run it from the terminal. If you want syntax highlighting and code completion there are plugins for popular code editors which provide these features.

The author prefers the VS Code editor, with the Nim extension which provides syntax highlighting and code completion, and the Code Runner extension for quick compiling and running.

If you’re using other code editors, see the wiki for available editor support.

Testing the installation

To check if the installation was successful, we will write a program which is traditionally used as an introductory example — Hello World.

Printing (as in: displaying on the screen; not on a paper with a printer) the phrase Hello World! in Nim is straightforward and it doesn’t require any boilerplate code.

In a new text file called e.g. helloworld.nim we need to write just one line of code:

helloworld.nim
echo "Hello World!"
The phrase you want to print must follow the echo command and must be enclosed in double-quotes (").

First we need to compile our program, and then run it to see if it works as expected.

Open your terminal in the same directory where your file is (on Linux you can get "Open Terminal here" if you right-click the directory in your file manager, on Windows you should use Shift + right-click to get the menu option for opening the command line).

We compile our program by typing in the terminal:

nim c helloworld.nim

After a successful compilation, we can run our program. On Linux we can run our program by typing ./helloworld in the terminal, and on Windows we do it by typing helloworld.exe.

There is also a possibility to both compile and run the program with just one command. We need to type:

nim c -r helloworld.nim
c is telling Nim to compile the file, and -r is telling it to run it immediately.
To see all compiler options, type nim --help in your terminal.

If you’re using VSCode with the Code Runner extension mentioned before, you’ll just have to press Ctrl+Alt+N and your file will be compiled and run.

Whichever way you chose to run your program, after a brief moment in the output window (or in your terminal) you should see:

Hello World!

Congratulations, you have successfully run your first Nim program!

Now you know how to print some stuff on the screen (using the echo command), compile your program (typing nim c programName.nim in your terminal), and run it (various possibilities).

We can now start to explore the basic elements which will help us to write simple Nim programs.


Naming values

It is often helpful to give the values in our programs names to help us keep track of things. If we ask a user for his/her name, we want to store it for the later usage, without asking for it again and again every time we need to do some computation with it.

In the example pi = 3.14, the name pi is connected to the value 3.14. From our experience, we can tell that the type of a variable pi is a (decimal) number.

Another example would be firstName = Alice, where firstName is the name of a variable with the value Alice. We would say that the type of this variable is a word.

In programming languages this works similarly. These name assignments have their name, the value, and a type.

Variable declaration

Nim is a statically typed programming language, meaning that the type of an assignment needs to be declared before using the value.

In Nim we also distinguish values that can change, or mutate, from those that can’t, but more on this later. We can declare a variable (a mutable assignment) using the var keyword, just by stating its name and type (the value can be added later) by using this syntax:

var <name>: <type>

If we already know its value, we can declare a variable and give it a value immediately:

var <name>: <type> = <value>
Angular brackets(<>) are used to show something you can change.
So <name> is not literally the word name in angular brackets but rather any name.

Nim also has type inference ability — the compiler can automatically detect the type of a name assignment from its value, without explicitly stating the type. We’ll look more into the various types in the next section.

So we can assign a variable without an explicit type like this:

var <name> = <value>

An example of this in Nim looks like this:

var a: int  (1)
var b = 7   (2)
1 Variable a is of type int (integer) with no value explicitly set.
2 Variable b has a value of 7 — its type is automatically detected as an integer.

 

When assigning names it is important to choose names that mean something for your program. Simply naming them a, b, c, and so forth will quickly become confusing. It is not possible to use spaces in a name, as that would split it into two. So if the name you choose consists of more than one word the usual way is to write it in camelCase style (notice that the first letter in a name should be lowercase).

Note however that Nim is both case- and underscore-insensitive meaning that helloWorld and hello_world would be the same name. The exception to this is the first character, which is case-sensitive. Names can also include both numbers and other UTF-8 characters, even emojis should you wish that, but keep in mind you and possibly others will have to type them.

 

Instead of typing var for each variable, multiple variables (not necessarily of the same type) can be declared in the same var block. In Nim, blocks are parts of code with the same indentation (same number of spaces before the first character), and the default indentation level is two spaces. You will see such blocks everywhere in a Nim program, not only for assigning names.

var
  c = -11
  d = "Hello"
  e = '!'
In Nim tabs are not allowed as indentation.
You can set up your code editor to convert pressing Tab to any number of spaces.
In VS Code, the default setting is to convert Tab to four spaces. This is easily overridden in settings (Ctrl+,) by setting "editor.tabSize": 2.

 

As previously mentioned variables are mutable, i.e. their value can change (multiple times), but their type must stay the same as declared.

var f = 7           (1)

f = -3              (2)
f = 19
f = "Hello" # error (3)(4)
1 Variable f has an initial value of 7 and its type is inferred as int.
2 The value of f is first changed to -3, and then to 19. Both of these are integers, the same as the original value.
3 Trying to change the value of f to "Hello" produces an error because Hello is not a number, and this would change the type of f from an integer to a string.
4 # error is a comment. Comments in Nim code are written after a # character — everything after it on the same line will be ignored.

Immutable assignment

Unlike variables declared with var keyword, two more types of assignment exist in Nim, whose value cannot change, one declared with the const keyword, and the other declared with the let keyword.

Const

The value of an immutable assignment declared with const keyword must be known at compile time (before the program is run).

For example, we can declare the acceleration of gravity as const g = 9.81 or pi as const pi = 3.14, as we know their values in advance and these values will not change during the execution of our program.

const g = 35
g = -27         # error (1)

var h = -5
const i = h + 7 # error (2)
1 The value of a constant cannot be changed.
2 Variable h is not evaluated at compile time (it is a variable and its value can change during the execution of a program), consequently the value of constant i can’t be known at compile time, and this will raise an error.

In some programming languages it is a common practice to have the names of constants written in ALL_CAPS. Constants in Nim are written just like any other variable.

Let

Immutable assignments declared with let don’t need to be known at compile time, their value can be set at any time during the execution of a program, but once it is set, their value cannot change.

let j = 35
j = -27 # error (1)

var k = -5
let l = k + 7   (2)
1 The value of an immutable cannot be changed.
2 In contrast to const example above, this works.

In practice, you will see/use let more frequently than const.

While you could use var for everything, your default choice should be let. Use var only for the variables which will be modified.


Basic data types

Integers

As seen in the previous section, integers are numbers which are written without a fractional component and without a decimal point.

For example: 32, -174, 0, 10_000_000 are all integers. Notice that we can use _ as a thousands separator, to make larger numbers more readable (it is easier to see that we’re talking about 10 million when it’s written as 10_000_000 rather than as 10000000).

The expected mathematical operators — addition (+), subtraction (-), multiplication (*), and division (/) — work as one would expect. The first three operations always produce integers, while dividing two integers always gives a floating point number (a number with a decimal point) as a result, even if two numbers can be divided without a remainder.

Integer division (division where the fractional part is discarded) can be achieved with the div operator. An operator mod is used if one is interested in the remainder (modulus) of an integer division. The result of these two operations is always an integer.

integers.nim
let
  a = 11
  b = 4

echo "a + b = ", a + b (1)
echo "a - b = ", a - b
echo "a * b = ", a * b
echo "a / b = ", a / b
echo "a div b = ", a div b
echo "a mod b = ", a mod b
1 The echo command will print to the screen everything that follows it separated by commas. In this case, it first prints the string a + b = , and then after it, in the same row, it prints the result of the expression a + b.

We can compile and run the above code, and the output should be:

a + b = 15
a - b = 7
a * b = 44
a / b = 2.75
a div b = 2
a mod b = 3

Floats

Floating-point numbers, or floats for short, are an approximate representation of real numbers.

For example: 2.73, -3.14, 5.0, 4e7 are floats. Notice that we can use scientific notation for large floats, where the number after the e is the exponent. In this example, 4e7 is a notation representing 4 * 10^7.

We can also use the four basic mathematical operations between two floats. Operators div and mod are not defined for floats.

floats.nim
let
  c = 6.75
  d = 2.25

echo "c + d = ", c + d
echo "c - d = ", c - d
echo "c * d = ", c * d
echo "c / d = ", c / d
c + d = 9.0  (1)
c - d = 4.5
c * d = 15.1875
c / d = 3.0  (1)
1 Notice that in the addition and division examples, even though we get a number without a decimal part, the result is still of the floating type.

 

The precedence of mathematical operations is as one would expect — multiplication and division have higher priority than addition and subtraction.

echo 2 + 3 * 4
echo 24 - 8 / 4
14
22.0

Converting floats and integers

Mathematical operations between variables of different numerical types are not possible in Nim, and they will produce an error:

let
  e = 5
  f = 23.456

echo e + f   # error

The values of variables need to be converted to the same type. Conversion is straight-forward — to convert to an integer, we use the int function, and to convert to a float the float function is used.

let
  e = 5
  f = 23.987

echo float(e)       (1)
echo int(f)         (2)

echo float(e) + f   (3)
echo e + int(f)     (4)
1 Printing a float version of an integer e. (e remains of integer type)
2 Printing an int version of a float f.
3 Both operands are floats and can be added.
4 Both operands are integers and can be added.
5.0
23
28.987
28
When using the int function to convert a float to an integer no rounding will be performed. The number simply drops any decimals.
To perform rounding we must call another function, but for that we must know a bit more about how to use Nim.

Characters

The char type is used for representing a single ASCII character.

Chars are written between two single ticks ('). Chars can be letters, symbols, or single digits. Multiple digits or multiple letters produce an error.

let
  h = 'z'
  i = '+'
  j = '2'
  k = '35' # error
  l = 'xy' # error

Strings

Strings can be described as a series of characters. Their content is written between two double quotes (").

We might think of strings as words, but they can contain more than one word, some symbols, or digits.

strings.nim
let
  m = "word"
  n = "A sentence with interpunction."
  o = ""    (1)
  p = "32"  (2)
  q = "!"   (3)
1 An empty string.
2 This is not a number (int). It is inside double quotes, making it a string.
3 Even though this is only one character, it is not a char because it is enclosed inside of double quotes.

Special characters

If we try to print the following string:

echo "some\nim\tips"

the result might surprise us:

some
im	ips

This is because there are several characters which have a special meaning. They are used by prepending the escape character \ to them.

  • \n is a newline character

  • \t is a tab character

  • \\ is a backslash (since one \ is used as the escape character)

If we wanted to print the above example as it was written, we have two possibilities:

  • Use \\ instead of \ to print backslashes, or

  • Use raw strings which have syntax r"…​" (putting a letter r immediately before the first quote), in which there are no escape characters and no special meanings — everything is printed as it is.

echo "some\\nim\\tips"
echo r"some\nim\tips"
some\nim\tips
some\nim\tips

There are more special characters than the ones listed above, and they are all found in the Nim manual.

String concatenation

Strings in Nim are mutable, meaning their content can change. With the add function we can add (append) either another string or a char to an existing string. If we don’t want to change the original string, we can also concatenate (join together) strings with the & operator, this returns a new string.

stringConcat.nim
var                     (1)
  p = "abc"
  q = "xy"
  r = 'z'

p.add("def")            (2)
echo "p is now: ", p

q.add(r)                (3)
echo "q is now: ", q

echo "concat: ", p & q  (4)

echo "p is still: ", p
echo "q is still: ", q
1 If we plan to modify strings, they should be declared as var.
2 Adding another string modifies the existing string p in-place, changing its value.
3 We can also add a char to a string.
4 Concatenating two strings produces a new string, without modifying the original strings.
p is now: abcdef
q is now: xyz
concat: abcdefxyz
p is still: abcdef
q is still: xyz

Boolean

A boolean (or just bool) data type can only have two values: true or false. Booleans are usually used for control flow (next section), and they are often a result of relational operators.

The usual naming convention for boolean variables is to write them as a simple yes/no (true/false) questions, e.g. isEmpty, isFinished, isMoving, etc.

Relational operators

Relational operators test the relation between two entities, which must be comparable.

To compare if two values are the same, == (two equal signs) is used. Do not confuse this with =, which is used for assignment as we saw earlier.

Here are all the relational operators defined for integers:

relationalOperators.nim
let
  g = 31
  h = 99

echo "g is greater than h: ", g > h
echo "g is smaller than h: ", g < h
echo "g is equal to h: ", g == h
echo "g is not equal to h: ", g != h
echo "g is greater or equal to h: ", g >= h
echo "g is smaller or equal to h: ", g <= h
g is greater than h: false
g is smaller than h: true
g is equal to h: false
g is not equal to h: true
g is greater or equal to h: false
g is smaller or equal to h: true

We can also compare characters and strings:

relationalOperators.nim
let
  i = 'a'
  j = 'd'
  k = 'Z'

echo i < j
echo i < k    (1)

let
  m = "axyb"
  n = "axyz"
  o = "ba"
  p = "ba "

echo m < n    (2)
echo n < o    (3)
echo o < p    (4)
1 All uppercase letters come before lowercase letters.
2 String comparison works char-by-char. First three characters are the same, and character b is smaller than character z.
3 String length doesn’t matter for comparison if their characters are not identical.
4 Shorter string is smaller than the longer one.
true
false
true
true
true

Logical operators

Logical operators are used to test the truthiness of an expression consisting of one or more boolean values.

  • Logical and returns true only if both members are true

  • Logical or returns true if there is at least one member which is true

  • Logical xor returns true if one member is true, but the other is not

  • Logical not negates the truthiness of its member — changing true to false, and vice versa (it is the only logical operator that takes just one operand)

logicalOperators.nim
echo "T and T: ", true and true
echo "T and F: ", true and false
echo "F and F: ", false and false
echo "---"
echo "T or T: ", true or true
echo "T or F: ", true or false
echo "F or F: ", false or false
echo "---"
echo "T xor T: ", true xor true
echo "T xor F: ", true xor false
echo "F xor F: ", false xor false
echo "---"
echo "not T: ", not true
echo "not F: ", not false
T and T: true
T and F: false
F and F: false
---
T or T: true
T or F: true
F or F: false
---
T xor T: false
T xor F: true
F xor F: false
---
not T: false
not F: true

Relational and logical operators can be combined together to form more complex expressions.

For example: (5 < 7) and (11 + 9 == 32 - 2*6) will become true and (20 == 20), which becomes true and true, and in the end this will give the final result of true.

Recap

This was the longest chapter in this tutorial and we covered a lot of ground. Take your time to go trough each data type and experiment with what you can do with each of them.

Types might seem like a restriction at first, but they allow the Nim compiler to both make your code faster, and make sure you’re not doing something wrong by accident.

Now you know the basic data types and several operations on them, which should be enough to do some simple calculations in Nim. Test your knowledge by doing the following exercises.

Exercises

  1. Create an immutable variable containing your age (in years). Print your age in days. (1 year = 365 days)

  2. Check if your age is divisible by 3. (Hint: use mod)

  3. Create an immutable variable containing your height in centimeters. Print your height in inches. (1 in = 2.54 cm)

  4. A pipe has a 3/8 inch diameter. Express the diameter in centimeters.

  5. Create an immutable variable containing your first name, and another one containing your last name. Make a variable fullName by concatenating the previous two variables. Don’t forget to put a whitespace in-between. Print your full name.

  6. Alice earns $400 every 15 days. Bob earns $3.14 per hour and works 8 hours a day, 7 days a week. After 30 days, has Alice earned more than Bob? (Hint: use relational operators)


Control flow

So far in our programs every line of code was executed at some point. Control flow statements allow us to have parts of code which will be executed only if some boolean condition is satisfied.

If we think of our program as a road we can think of control flow as various branches, and we pick our path depending on some condition. For example, we will buy eggs only if their price is less than some value. Or, if it is raining, we will bring an umbrella, otherwise (else) we will bring sunglasses.

Written in pseudocode, these two examples would look like this:

if eggPrice < wantedPrice:
  buyEggs

if isRaining:
  bring umbrella
else:
  bring sunglasses

Nim syntax is very similar, as you’ll see below.

If statement

An if statement as shown above is the simplest way to branch our program.

The Nim syntax for writing if statement is:

if <condition>:     (1)
  <indented block>  (2)
1 The condition must be of boolean type — either a boolean variable or a relational and/or logical expression.
2 All lines following the if line which are indented two spaces make the same block and will be executed only if the condition is true.

If statements can be nested, i.e. inside one if-block there can be another if statement.

if.nim
let
  a = 11
  b = 22
  c = 999

if a < b:
  echo "a is smaller than b"
  if 10*a < b:  (1)
    echo "not only that, a is *much* smaller than b"

if b < c:
  echo "b is smaller than c"
  if 10*b < c:  (2)
    echo "not only that, b is *much* smaller than c"

if a+b > c:     (3)
  echo "a and b are larger than c"
  if 1 < 100 and 321 > 123:   (4)
    echo "did you know that 1 is smaller than 100?"
    echo "and 321 is larger than 123! wow!"
1 The first condition is true, the second is false — inner echo is not executed.
2 Both conditions are true and both lines are printed.
3 The first condition is false — all lines inside of its block will be skipped, nothing is printed.
4 Using the logical and inside of the if statement.
a is smaller than b
b is smaller than c
not only that, b is *much* smaller than c

Else

Else follows after an if-block and allows us to have a branch of code which will be executed when the condition in the if statement is not true.

else.nim
let
  d = 63
  e = 2.718

if d < 10:
  echo "d is a small number"
else:
  echo "d is a large number"

if e < 10:
  echo "e is a small number"
else:
  echo "e is a large number"
d is a large number
e is a small number
If you only want to execute a block if the statement is false, you can simply negate the condition with the not operator.

Elif

Elif is short for "else if", and enables us to chain multiple if statements together.

The program tests every statement until it finds one which is true. After that, all further statements are ignored.

elif.nim
let
  f = 3456
  g = 7

if f < 10:
  echo "f is smaller than 10"
elif f < 100:
  echo "f is between 10 and 100"
elif f < 1000:
  echo "f is between 100 and 1000"
else:
  echo "f is larger than 1000"

if g < 1000:
  echo "g is smaller than 1000"
elif g < 100:
  echo "g is smaller than 100"
elif g < 10:
  echo "g is smaller than 10"
f is larger than 1000
g is smaller than 1000
In the case of g, even though g satisfies all three conditions, only the first branch is executed, automatically skipping all the other branches.

Case

A case statement is another way to only choose one of multiple possible paths, similar to the if statement with multiple elifs. A case statement, however, doesn’t take multiple boolean conditions, but rather any value with distinct states and a path for each possible value.

Code written with in if-elif block looking like this:

if x == 5:
  echo "Five!"
elif x == 7:
  echo "Seven!"
elif x == 10:
  echo "Ten!"
else:
  echo "unknown number"

can be written with case statement like this:

case x
of 5:
  echo "Five!"
of 7:
  echo "Seven!"
of 10:
  echo "Ten!"
else:
  echo "unknown number"

Unlike the if statement, case statement must cover all possible cases. If one is not interested in some of those cases, else: discard can be used.

case.nim
let h = 'y'

case h
of 'x':
  echo "You've chosen x"
of 'y':
  echo "You've chosen y"
of 'z':
  echo "You've chosen z"
else: discard   (1)
1 Even though we are interested in only three values of h, we must include this line to cover all other possible cases (all other characters). Without it, the code would not compile.
You've chosen y

 

We can also use multiple values for each branch if the same action should happen for more than one value.

multipleCase.nim
let i = 7

case i
  of 0:
    echo "i is zero"
  of 1, 3, 5, 7, 9:
    echo "i is odd"
  of 2, 4, 6, 8:
    echo "i is even"
  else:
    echo "i is too large"
i is odd

Loops

Loops are another control flow construct which allow us to run some parts of code multiple times. This can either be a known number of times (for-loops) or for as long as some condition is satisfied (while-loops).

For loop

Syntax of a for-loop is:

for <loopVariable> in <iterable>:
  <loop body>

Traditionally, i is often used as a loopVariable name, but any other name can be used. That variable will be available only inside the loop. Once the loop has finished, the value of the variable is discarded.

The iterable is any object we can iterate through. Of the types already mentioned, strings are iterable objects. (More iterable types will be introduced in the next section.)

All lines in the loop body are executed at every loop, which allows us to efficiently write repeating parts of code.

 

If we want to iterate through a range of (integer) numbers in Nim, the syntax for the iterable is start .. finish where start and finish are numbers. This will iterate through all the numbers between start and finish, including both start and finish. For the default range iterable, start needs to be smaller than finish.

If we want to iterate until a number (not including it), we can use ..<:

for1.nim
for n in 5 .. 9:  (1)
  echo n

echo ""

for n in 5 ..< 9: (2)
  echo n
1 Iterating through a range of numbers using .. — both ends are included in the range.
2 Iterating through the same range using ..< — it iterates until the higher end, not including it.
5
6
7
8
9

5
6
7
8

 

If we want to iterate through a range of numbers with a step size different than one, countup is used. With countup we define the starting value, the stopping value (included in the range), and the step size.

for2.nim
for n in countup(0, 16, 4):     (1)
  echo n
1 Counting up from zero to 16, with a step size of 4. The end (16) is included in the range.
0
4
8
12
16

 

To iterate through a range of numbers where the start is larger than finish, a similar function called countdown is used. Even if we’re counting down, the step size must be positive.

for2.nim
for n in countdown(4, 0):       (1)
  echo n

echo ""

for n in countdown(-3, -9, 2):  (2)
  echo n
1 To iterate from a higher to a lower number, we must use countdown (The .. operator can only be used when the starting value is smaller than the end value).
2 Even when counting down, the step size must be a positive number.
4
3
2
1
0

-3
-5
-7
-9

 

Since string is an iterable, we can use a for-loop to iterate through each character of the string (this kind of iteration is sometimes called a for-each loop).

for3.nim
let word = "alphabet"

for letter in word:
  echo letter
a
l
p
h
a
b
e
t

 

If we also need to have an iteration counter (starting from zero), we can achieve that by using for <counterVariable>, <loopVariable> in <iterator>: syntax. This is very practical if you want to iterate through one iterable, and simultaneously access another iterable at the same offset.

for3.nim
for i, letter in word:
  echo "letter ", i, " is: ", letter
letter 0 is: a
letter 1 is: l
letter 2 is: p
letter 3 is: h
letter 4 is: a
letter 5 is: b
letter 6 is: e
letter 7 is: t

While loop

While loops are similar to if statements, but they keep executing their block of code as long as the condition remains true. They are used when we don’t know in advance how many times the loop will run.

We must make sure the loop will terminate at some point and not become an infinite loop.

while.nim
var a = 1

while a*a < 10: (1)
  echo "a is: ", a
  inc a         (2)

echo "final value of a: ", a
1 This condition will be checked every time before entering the new loop and executing the code inside of it.
2 inc is used to increment a by one. It is the same as writing a = a + 1 or a += 1.
a is: 1
a is: 2
a is: 3
final value of a: 4

Break and continue

The break statement is used to prematurely exit from a loop, usually if some condition is met.

In the next example, if there were no if statement with break in it, the loop would continue to run and print until i becomes 1000. With the break statement, when i becomes 3, we immediately exit the loop (before printing the value of i).

break.nim
var i = 1

while i < 1000:
  if i == 3:
    break
  echo i
  inc i
1
2

 

The continue statement starts the next iteration of a loop immediately, without executing the remaining lines of the current iteration. Notice how 3 and 6 are missing from the output of the following code:

continue.nim
for i in 1 .. 8:
  if (i == 3) or (i == 6):
    continue
  echo i
1
2
4
5
7
8

Exercises

  1. Collatz conjecture is a popular mathematical problem with simple rules. First pick a number. If it is odd, multiply it by three and add one; if it is even, divide it by two. Repeat this procedure until you arrive at one. E.g. 5 → odd → 3*5 + 1 = 16 → even → 16 / 2 = 8 → even → 4 → 2 → 1 → end!
    Pick an integer (as a mutable variable) and create a loop which will print every step of the Collatz conjecture. (Hint: use div for division)

  2. Create an immutable variable containing your full name. Write a for-loop which will iterate through that string and print only the vowels (a, e, i, o, u). (Hint: use case statement with multiple values per branch)

  3. Fizz buzz is a kids game sometimes used to test basic programming knowledge. We count numbers from one upwards. If a number is divisible by 3 replace it with fizz, if it is divisible by 5 replace it with buzz, and if a number is divisible by 15 (both 3 and 5) replace it with fizzbuzz. First few rounds would look like this: 1, 2, fizz, 4, buzz, fizz, 7, …​
    Create a program which will print first 30 rounds of Fizz buzz. (Hint: beware of the order of divisibility tests)

  4. In the previous exercises you have converted inches to centimeters, and vice versa. Create a conversion table with multiple values. For example, the table might look like this:

in	| cm
----------------
1	| 2.54
4	| 10.16
7	| 17.78
10	| 25.4
13	| 33.02
16	| 40.64
19	| 48.26

Containers

Containers are data types which contain a collection of items and allow us to access those elements. Typically a container is also iterable, meaning that we can use them the same way we used strings in the loop chapter.

For example, a grocery list is a container of items we want to buy, and a list of primes is a container of numbers. Written in pseudocode:

groceryList = [ham, eggs, bread, apples]
primes = [1, 2, 3, 5, 7]

Arrays

An array is the simplest container type. Arrays are homogenous, i.e. all elements in an array must have the same type. Arrays are also of a constant size, meaning that the amount of elements (or rather the amount of possible elements), must be known at compile-time. This means that we call arrays a "homogenous container of a constant length".

The array type is declared using array[<length>, <type>], where length is the total capacity of the array (number of elements it can fit), and type is a type of all its elements. The declaration can be omitted if both length and type can be inferred from the passed elements.

The elements of an array are enclosed inside of square brackets.

var
  a: array[3, int] = [5, 7, 9]
  b = [5, 7, 9]               (1)
  c = []  # error             (2)
  d: array[7, string]         (3)
  x = 5
1 If we provide the values, the length and type of array b are known at compile time. Although correct, there is no need to specifically declare it like array a.
2 Neither the length nor the type of the elements can be inferred from this kind of declaration — this produces an error.
3 The correct way to declare an empty array (which will be filled later) is to give its length and type, without providing the values of its elements — array d can contain seven strings.

Since the length of an array has to be known at compile-time this will not work:

let n = 5

var a: array[n, char] # error (1)
1 This produces an error because n is declared using let — its value is not known at compile time. We can only use values declared with const as a length parameter for an array initialization.

Sequences

Sequences are containers similar to arrays, but their length doesn’t have to be known at compile time, and it can change during runtime — we declare only the type of the contained elements with seq[<type>]. Sequences are also homogenous — every element in a sequence has to be the same type.

The elements of a sequence are enclosed between @[ and ].

var
  e1: seq[int] = @[]    (1)
  f = @["abc", "def"]   (2)
1 The type of an empty sequence must be declared.
2 The type of a non-empty sequence can be inferred. In this case, it is a sequence containing strings.

Another way to initialize an empty sequence is to call the newSeq procedure. We’ll look more at procedure calls in the next section but for now just know that this is also a possibility:

var
  e = newSeq[int]()    (1)
1 Providing the type parameter inside of square brackets allows the procedure to know that it shall return a sequence of a certain type.
A frequent error is omission of the final (), which must be included.

 

We can add new elements to a sequence with the add function, similar to how we did with strings. For this to work the sequence must be mutable (defined with var), and the element we’re adding must be of the same type as the elements in the sequence.

seq.nim
var         (1)
  g = @['x', 'y']
  h = @['1', '2', '3']

g.add('z')  (2)
echo g

h.add(g)    (3)
echo h
1 Mutable type.
2 Adding a new element of the same type (char).
3 Adding another sequence containing the same type.
@['x', 'y', 'z']
@['1', '2', '3', 'x', 'y', 'z']

Trying to pass different types to the existing sequences will produce an error:

var i = @[9, 8, 7]

i.add(9.81) # error (1)
g.add(i)    # error (2)
1 Trying to add a float to a sequence of int.
2 Trying to add a sequence of int to a sequence of char.

 

Since sequences can vary in length we need a way to get their length, for this we can use the len function.

var i = @[9, 8, 7]
echo i.len

i.add(6)
echo i.len
3
4

Indexing and slicing

Indexing allows us to get a specific element from a container by its index. Think of the index as a position inside of the container.

Nim, like many other programming languages, has zero-based indexing, meaning that the first element in a container has the index zero, the second element has the index one, etc.

If we want to index "from the back", it is done by using the ^ prefix. The last element (first from the back) has index ^1.

The syntax for indexing is <container>[<index>].

indexing.nim
let j = ['a', 'b', 'c', 'd', 'e']

echo j[1]   (1)
echo j[^1]  (2)
1 Zero-based indexing: the element at index 1 is b.
2 Getting the last element.
b
e

 

Slicing allows us to get a series of elements with one call. It uses the same syntax as ranges (introduced in the for loop section).

If we use start .. stop syntax, both ends are included in the slice. Using start ..< stop syntax, the stop index is not included in the slice.

The syntax for slicing is <container>[<start> .. <stop>].

indexing.nim
echo j[0 .. 3]
echo j[0 ..< 3]
@[a, b, c, d]
@[a, b, c]

 

Both indexing and slicing can be used to assign new values to the existing mutable containers and strings.

assign.nim
var
  k: array[5, int]
  l = @['p', 'w', 'r']
  m = "Tom and Jerry"

for i in 0 .. 4:    (1)
  k[i] = 7 * i
echo k

l[1] = 'q'          (2)
echo l

m[8 .. 9] = "Ba"    (3)
echo m
1 Array of length 5 has indexes from zero to four. We will assign a value to each element of the array.
2 Assigning (changing) the second element (index 1) of a sequence.
3 Changing characters of a string at indexes 8 and 9.
[0, 7, 14, 21, 28]
@['p', 'q', 'r']
Tom and Barry

Tuples

Both of the containers we’ve seen so far have been homogenous. Tuples, on the other hand, contain heterogenous data, i.e. elements of a tuple can be of different types. Similarly to arrays, tuples have fixed-size.

The elements of a tuple are enclosed inside of parentheses.

tuples.nim
let n = ("Banana", 2, 'c')  (1)
echo n
1 Tuples can contain fields of different types. In this case: string, int, and char.
(Field0: "Banana", Field1: 2, Field2: 'c')

We can also name each field in a tuple to distinguish them. This can be used for accessing the elements of the tuple, instead of indexing.

tuples.nim
var o = (name: "Banana", weight: 2, rating: 'c')

o[1] = 7          (1)
o.name = "Apple"  (2)
echo o
1 Changing the value of a field by using the field’s index.
2 Changing the value of a field by using the field’s name.
(name: "Apple", weight: 7, rating: 'c')

Exercises

  1. Create an empty array which can contain ten integers.

    • Fill that array with numbers 10, 20, …​, 100. (Hint: use loops)

    • Print only the elements of that array that are on odd indices (values 20, 40, …​).

    • Multiply elements on even indices by 5. Print the modified array.

  2. Re-do the Collatz conjecture exercise, but this time instead of printing each step, add it to a sequence.

    • Pick a starting number. Interesting choices, among others, are 9, 19, 25 and 27.

    • Create a sequence whose only member is that starting number

    • Using the same logic as before, keep adding elements to the sequence until you reach 1

    • Print the length of the sequence, and the sequence itself

  3. Find the number in a range from 2 to 100 which will produce the longest Collatz sequence.

    • For each number in the given range calculate its Collatz sequence

    • If the length of current sequence is longer than the previous record, save the current length and the starting number as a new record (you can use the tuple (longestLength, startingNumber) or two separate variables)

    • Print the starting number which gives the longest sequence, and its length


Procedures

Procedures, or functions as they are called in some other programming languages, are parts of code that perform a specific task, packaged as a unit. The benefit of grouping code together like this is that we can call these procedures instead of writing all the code over again when we wish to use the procedure’s code.

In some of the previous chapters we’ve looked at the Collatz conjecture in various different scenarios. By wrapping up the Collatz conjecture logic into a procedure we could have called the same code for all the exercises.

So far we have used many built-in procedures, such as echo for printing, add for adding elements to a sequence, inc to increase the value of an integer, len to get the length of a container, etc. Now we’ll see how to create and use our own procedures.

Some of the advantages of using procedures are:

  • Reducing code duplication

  • Easier to read code as we can name pieces by what they do

  • Decomposing a complex task into simpler steps

As mentioned in the beginning of this section, procedures are often called functions in other languages. This is actually a bit of a misnomer if we consider the mathematical definition of a function. Mathematical functions take a set of arguments (like f(x)) and always return the same answer for the same input.

Programmatic procedures on the other hand don’t always return the same output for a given input. Sometimes they don’t return anything at all. This is because our computer programs can store state in the variables we mentioned earlier which procedures can read and change. In Nim the word func is currently reserved to be used as the more mathematically correct kind of function, forcing no side-effects.

Declaring a procedure

Before we can use (call) our procedure, we need to create it and define what it does.

A procedure is declared by using the proc keyword and the procedure name, followed by the input parameters and their type inside of parentheses, and the last part is a colon and the type of the value returned from a procedure, like this:

proc <name>(<p1>: <type1>, <p2>: <type2>, ...): <returnType>

The body of a procedure is written in the indented block following the declaration appended with a = sign.

callProcs.nim
proc findMax(x: int, y: int): int = (1)
  if x > y:
    return x    (2)
  else:
    return y
  # this is inside of the procedure
# this is outside of the procedure
1 Declaring procedure called findMax, which has two parameters, x and y, and it returns an int type.
2 To return a value from a procedure, we use the return keyword.

 

proc echoLanguageRating(language: string) = (1)
  case language
  of "Nim", "nim", "NIM":
    echo language, " is the best language!"
  else:
    echo language, " might be a second-best language."
1 The echoLanguageRating procedure just echoes the given name, it doesn’t return anything, so the return type is not declared.

 

Normally we’re not allowed to change any of the parameters we are given. Doing something like this will throw an error:

proc changeArgument(argument: int) =
  argument += 5

var ourVariable = 10
changeArgument(ourVariable)

In order for this to work we need to allow Nim, and the programmer using our procedure, to change the argument by declaring it as a variable:

proc changeArgument(argument: var int) = (1)
  argument += 5

var ourVariable = 10
changeArgument(ourVariable)
echo ourVariable
changeArgument(ourVariable)
echo ourVariable
1 Notice how argument is now declared as a var int and not just as an int.
15
20

This of course means that the name we pass it must be declared as a variable as well, passing in something assigned with const or let will throw an error.

While it’s good practice to pass things as arguments it’s also possible to use names declared outside the procedure, both variables and constants:

var x = 100

proc echoX() =
  echo x       (1)
  x += 1       (2)

echoX()
echoX()
1 Here we access the outside variable x.
2 We can also update its value, since it’s declared as a variable.
100
101

Calling the procedures

After we have declared a procedure, we can call it. The usual way of calling procedures/functions in many programming languages is to state its name and provide the arguments in the parentheses, like this:

<procName>(<arg1>, <arg2>, ...)

The result from calling a procedure can be stored in a variable.

If we want to call our findMax procedure from the above example, and save the return value in a variable we can do that with:

callProcs.nim
let
  a = findMax(987, 789)
  b = findMax(123, 321)
  c = findMax(a, b)     (1)

echo a
echo b
echo c
1 The result from the function findMax is here named c, and is called with the results of our first two calls (findMax(987, 321)).
987
321
987

 

Nim, unlike many other languages, also supports Uniform Function Call Syntax, which allows many different ways of calling procedures.

This one is a call where the first argument is written before the function name, and the rest of the parameters are stated in parentheses:

<arg1>.<procName>(<arg2>, ...)

We have used this syntax when we were adding elements to an existing sequence (<seq>.add(<element>)), as this makes it more readable and expresses our intent more clearly than writing add(<seq>, <element>). We can also omit the parentheses around the arguments:

<procName> <arg1>, <arg2>, ...

We’ve seen this style being used when we call the echo procedure, and when calling the len procedure without any arguments. These two can also be combined like this, but this syntax however is not seen very often:

<arg1>.<procName> <arg2>, <arg3>, ...

 

The uniform call syntax allows for more readable chaining of multiple procedures:

ufcs.nim
proc plus(x, y: int): int =  (1)
  return x + y

proc multi(x, y: int): int =
  return x * y

let
  a = 2
  b = 3
  c = 4

echo a.plus(b) == plus(a, b)
echo c.multi(a) == multi(c, a)


echo a.plus(b).multi(c)  (2)
echo c.multi(b).plus(a)  (3)
1 If multiple parameters are of the same type, we can declare their type in this compact way.
2 First we add a and b, then the result of that operation (2 + 3 = 5) is passed as the first parameter to the multi procedure, where it is multiplied by c (5 * 4 = 20).
3 First we multiply c and b, then the result of that operation (4 * 3 = 12) is passed as the first parameter to the plus procedure, where it is added with a (12 + 2 = 14).
true
true
20
14

Result variable

In Nim, every procedure that returns a value has an implicitly declared and initialized (with a default value) result variable. The procedure will return the value of this result variable when it reaches the end of its indented block, even with no return statement.

result.nim
proc findBiggest(a: seq[int]): int =  (1)
  for number in a:
    if number > result:
      result = number
  # the end of proc                   (2)

let d = @[3, -5, 11, 33, 7, -15]
echo findBiggest(d)
1 The return type is int. The result variable is initialized with the default value for int — 0.
2 When the end of the procedure is reached, the value of result is returned.
33

 

Beware! The default value of strings and sequences is nil, and when we use them as returning types, the result variable needs to be initialized as an empty string ("") or as an empty sequence (@[]).
result.nim
proc keepOdds(a: seq[int]): seq[int] =
  result = @[]            (1)
  for number in a:
    if number mod 2 == 1:
      result.add(number)


let f = @[1, 6, 4, 43, 57, 34, 98]
echo keepOdds(f)
1 Sequences must be initialized, deleting this line will cause an error. Notice that var must not be used, as result is already implicitly declared.
@[1, 43, 57]

 

Inside of a procedure we can also call other procedures.

filterOdds.nim
proc isDivisibleBy3(x: int): bool =
  return x mod 3 == 0

proc filterMultiplesOf3(a: seq[int]): seq[int] =
  result = @[]              (1)
  for i in a:
    if i.isDivisibleBy3():  (2)
      result.add(i)


let
  g = @[2, 6, 5, 7, 9, 0, 5, 3]
  h = @[5, 4, 3, 2, 1]
  i = @[626, 45390, 3219, 4210, 4126]

echo filterMultiplesOf3(g)
echo h.filterMultiplesOf3()
echo filterMultiplesOf3 i   (3)
1 Initializing the result variable as an empty sequence. Because the return value is declared as seq[int], result can only accept integers.
2 Calling the previously declared procedure. Its return type is bool and can be used in the if-statement.
3 The third way of calling a procedure, as we saw above.
@[6, 9, 0, 3]
@[3]
@[45390, 3219]

Forward declaration

As mentioned in the very beginning of this section we can declare a procedure without a code block. The reason for this is that we have to declare procedures before we can call them, doing this will not work:

echo 5.plus(10) # error      (1)

proc plus(x, y: int): int =  (2)
  return x + y
1 This will throw an error as plus isn’t defined yet.
2 Here we define plus, but since it’s after we use it Nim doesn’t know about it yet.

The way to get around this is what’s called a forward declaration:

proc plus(x, y: int): int    (1)

echo 5.plus(10)              (2)

proc plus(x, y: int): int =  (3)
  return x + y
1 Here we tell Nim that it should consider the plus procedure to exist with this definition.
2 Now we are free to use it in our code, this will work.
3 This is were plus is actually implemented, this must of course match our previous definition.

Exercises

  1. Create a procedure which will greet a person (print "Hello <name>") based on the provided name. Create a sequence of names. Greet each person using the created procedure.

  2. Create a procedure findMax3 which will return the largest of three values.

  3. Points in 2D plane can be represented as tuple[x, y: float]. Write a procedure which will receive two points and return a new point which is a sum of those two points (add x’s and y’s separately).

  4. Create two procedures tick and tock which echo out the words "tick" and "tock". Have a global variable to keep track of how many times they have run, and run one from the other until the counter reaches 20. The expected output is to get lines with "tick" and "tock" alternating 20 times. (Hint: use forward declarations.)

You can press Ctrl+C to stop execution of a program if you enter an infinite loop.

Test all procedures by calling them with different parameters.


Modules

So far we have used the functionality which is available by default every time we start a new Nim file. This can be extended with modules, which give more functionality for some specific topic.

Some of the most used Nim modules are:

  • strutils: additional functionality when dealing with strings

  • sequtils: additional functionality for sequences

  • math: mathematical functions (logarithms, square roots, …​), trigonometry (sin, cos, …​)

  • times: measure and deal with time

But there are many more, both in what’s called the standard library and in the nimble package manager.

Importing a module

If we want to import a module and all of its functionality, all we have to do is put import <moduleName> in our file. This is commonly done on the top so we can easily see what our code uses.

stringutils.nim
import strutils       (1)

let
  a = "My string with whitespace."
  b = '!'

echo a.split()        (2)
echo a.toUpperAscii() (3)
echo b.repeat(5)      (4)
1 Importing strutils.
2 Using split from strutils module. It splits the string in a sequence of words.
3 toUpperAscii converts all ASCII letters to uppercase.
4 repeat is also from strutils module, and it repeats either a character or a whole string the requested amount of times.
@["My", "string", "with", "whitespace."]
MY STRING WITH WHITESPACE.
!!!!!
maths.nim
import math                 (1)

let
  c = 30.0 # degrees
  cRadians = c.degToRad()   (2)

echo cRadians
echo sin(cRadians).round(2) (3)

echo 2^5                    (4)
1 Importing math.
2 Converting degrees to radians with degToRad.
3 sin takes radians. We round (also from math module) the result to at most 2 decimal places. (Otherwise the result would be: 0.4999999999999999)
4 Math module also has ^ operator for calculating powers of a number.
0.5235987755982988
0.5
32

Creating our own

Often times we have so much code in a project that it makes sense to split it into pieces that each does a certain thing. If you create two files side by side in a folder, let’s call them firstFile.nim and secondFile.nim, you can import one from the other as a module:

firstFile.nim
proc plus*(a, b: int): int = (1)
  return a + b

proc minus(a, b: int): int = (2)
  return a - b
1 Notice how the plus procedure now has an asterisk (*) after it’s name, this tells Nim that another file importing this one will be able to use this procedure.
2 By contrast this will not be visible when importing this file.
secondFile.nim
import firstFile          (1)

echo plus(5, 10)          (2)
echo minus(10, 5) # error (3)
1 Here we import firstFile.nim. We don’t need to put the .nim extension on here.
2 This will work fine and output 15 as it’s declared in firstFile and visible to us.
3 However this will throw an error as the minus procedure is not visible since it doesn’t have an asterisk behind it’s name.

Interacting with user input

Using the stuff we’ve introduced so far (basic data types and containers, control flow, loops) allows us to make quite a few simple programs. To make our programs more interactive, we need an option to read data from a file, or ask a user for an input.

Reading from a file

Let’s say we have a text file called people.txt in the same directory as our Nim code. The contents of that file looks like this:

people.txt
Alice A.
Bob B.
Carol C.

We want to use the contents of that file in our program, as a list (sequence) of names.

readFromFile.nim
import strutils

let contents = readFile("people.txt") (1)
echo contents

let people = contents.splitLines()    (2)
echo people
1 To read contents of a file, we use the readFile procedure, and we provide a path to the file from which to read (if the file is in the same directory as our Nim program, providing a filename is enough). The result is a multiline string.
2 To split a multiline string into a sequence of strings (each string contains all the contents of a single line) we use splitLines from the strutils module.
Alice A.
Bob B.
Carol C.
            (1)
@["Alice A.", "Bob B.", "Carol C.", ""] (2)
1 There was a final new line (empty last line) in the original file, which is also present here.
2 Because of the final new line, our sequence is longer than we expected/wanted.

To solve the problem of a final new line, we can use the strip procedure from strutils after we have read from a file. All this does is remove any so-called whitespace from the start and end of our string. Whitespace is simply any character that makes some space, new-lines, spaces, tabs, etc.

readFromFile2.nim
import strutils

let contents = readFile("people.txt").strip() (1)
echo contents

let people = contents.splitLines()
echo people
1 Using strip provides the expected results.
Alice A.
Bob B.
Carol C.
@["Alice A.", "Bob B.", "Carol C."]

Reading user input

If we want to interact with a user, we must be able to ask them for an input, and then process it and use it. We need to read from standard input (stdin) by passing stdin to the readLine procedure.

interaction1.nim
echo "Please enter your name:"
let name = readLine(stdin)  (1)

echo "Hello ", name, ", nice to meet you!"
1 The type of name is inferred to be a string.
Please enter your name:
        (1)
1 Waiting for user input. After we write our name and press Enter, the program will continue.
Please enter your name:
Alice
Hello Alice, nice to meet you!
VS Code users cannot run this the usual way (using Ctrl+Alt+N) because output window doesn’t allow user inputs. This needs to be run in the terminal.

Dealing with numbers

Reading from a file or from a user input always gives a string as a result. If we would like to use numbers, we need to convert strings to numbers — we again use the strutils module and use parseInt to convert to integers or parseFloat to convert into a float.

interaction2.nim
import strutils

echo "Please enter your year of birth:"
let yearOfBirth = readLine(stdin).parseInt() (1)

let age = 2018 - yearOfBirth

echo "You are ", age, " years old."
1 Convert a string to an integer. When written like this, we trust our user to give a valid integer. What would happen if a user inputs '79 or ninety-three? Try it yourself.
Please enter your year of birth:
1934
You are 84 years old.

 

If we have file numbers.txt in the same directory as our Nim code, with the following content:

numbers.txt
27.3
98.24
11.93
33.67
55.01

And we want to read that file and find the sum and average of the numbers provided, we can do something like this:

interaction3.nim
import strutils, sequtils, math         (1)

let
  strNums = readFile("numbers.txt").strip().splitLines()  (2)
  nums = strNums.map(parseFloat)        (3)

let
  sumNums = sum(nums)                   (4)
  average = sumNums / float(nums.len)   (5)

echo sumNums
echo average
1 We import multiple modules. strutils gives us strip and splitLines, sequtils gives map, and math gives sum.
2 We strip the final new line, and split lines to create a sequence of strings.
3 map works by applying a procedure (in this case parseFloat) to each member of a container. In other words, we convert each string to a float, returning a new sequence of floats.
4 Using sum from math module to give us the sum of all elements in a sequence.
5 We need to convert the length of a sequence to float, because sumNums is a float.
226.15
45.23

Exercises

  1. Ask a user for their height and weight. Calculate their BMI. Report them the BMI value and the category.

  2. Repeat Collatz conjecture exercise so your program asks a user for a starting number. Print the resulting sequence.

  3. Ask a user for a string they want to have reversed. Create a procedure which takes a string and returns a reversed version. For example, if user types Nim-lang, the procedure should return gnal-miN. (Hint: use indexing and countdown)


Conclusion

It is time to conclude this tutorial. Hopefully this has been useful to you, and you managed to make your first steps in programming and/or the Nim programming language.

These have only been the basics and we’ve only scratched the surface, but this should be enough to enable you to make simple programs and solve some simple tasks or puzzles. Nim has a lot more to offer, and hopefully you will continue to explore its possibilities.

Next steps

If you want to continue learning from Nim tutorials:

If you want to solve some programming puzzles:

  • Advent of Code: Series of interesting puzzles released every December. Archive of old puzzles (from 2015 onwards) is available.

  • Project Euler: Mostly mathematical tasks.

 

Happy coding!

 

 


 

The source files are available on Github.