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.
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 no or 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?
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.
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.19.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.
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:
echo "Hello World!"
The phrase you want to print must follow the
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
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
To see all compiler options, type
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:
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.
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
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
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.
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>
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)
When assigning names it is important to choose names that mean something for your program.
Simply naming them
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
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
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
In VS Code, the default setting is to convert
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)
|2||The value of
|3||Trying to change the value of
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
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.|
In some programming languages it is a common practice to have the names of constants written in
Constants in Nim are written just like any other variable.
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
In practice, you will see/use
let more frequently than
While you could use
var for everything, your default choice should be
var only for the variables which will be modified.
Basic data types
As seen in the previous section, integers are numbers which are written without a fractional component and without a decimal point.
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 usual 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
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.
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
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
Floating-point numbers, or floats for short, are an approximate representation of real numbers.
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.
mod are not defined for floats.
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
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)
|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
To perform rounding we must call another function, but for that we must know a bit more about how to use Nim.
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 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.
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.|
If we try to print the following string:
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.
\nis a newline character
\tis 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:
\to print backslashes, or
Use raw strings which have syntax
r"…"(putting a letter
rimmediately 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"
There are more special characters than the ones listed above, and they are all found in the Nim manual.
Strings in Nim are mutable, meaning their content can change.
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.
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
|2||Adding another string modifies the existing string
|3||We can also add a
|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
A boolean (or just
bool) data type can only have two values:
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.
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:
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:
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
|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 are used to test the truthiness of an expression consisting of one or more boolean values.
trueonly if both members are
trueif there is at least one member which is
trueif one member is true, but the other is not
notnegates the truthiness of its member: changing
false, and vice versa (it is the only logical operator that takes just one operand)
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.
(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
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.
Create an immutable variable containing your age (in years). Print your age in days. (1 year = 365 days)
Check if your age is divisible by 3. (Hint: use
Create an immutable variable containing your height in centimeters. Print your height in inches. (1 in = 2.54 cm)
A pipe has a 3/8 inch diameter. Express the diameter in centimeters.
Create an immutable variable containing your first name, and another one containing your last name. Make a variable
fullNameby concatenating the previous two variables. Don’t forget to put a whitespace in-between. Print your full name.
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)
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.
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)
|2||All lines following the
If statements can be nested, i.e. inside one if-block there can be another if statement.
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
|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
a is smaller than b b is smaller than c not only that, b is *much* smaller than c
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.
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
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.
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
A case statement is another way to only choose one of multiple possible paths, similar to the if statement with multiple
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.
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
You've chosen y
We can also use multiple values for each branch if the same action should happen for more than one value.
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 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).
Syntax of a for-loop is:
for <loopVariable> in <iterable>: <loop body>
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.
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
start .. finish where
finish are numbers.
This will iterate through all the numbers between
finish, including both
For the default range iterable,
start needs to be smaller than
If we want to iterate until a number (not including it), we can use
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
|2||Iterating through the same range using
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.
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.
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
|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).
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.
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 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.
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.|
a is: 1 a is: 2 a is: 3 final value of a: 4
Break and continue
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.
break statement, when
i becomes 3, we immediately exit the loop (before printing the value of
var i = 1 while i < 1000: if i == 3: break echo i inc i
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:
for i in 1 .. 8: if (i == 3) or (i == 6): continue echo i
1 2 4 5 7 8
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
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
casestatement with multiple values per branch)
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)
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 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]
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
|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
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
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
Sequences are also homogenous, i.e. every element in a sequence has to be the same type.
The elements of a sequence are enclosed between
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
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.
var (1) g = @['x', 'y'] h = @['1', '2', '3'] g.add('z') (2) echo g h.add(g) (3) echo h
|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
|2||Trying to add a sequence of
Since sequences can vary in length we need a way to get their length, for this we can use the
var i = @[9, 8, 7] echo i.len i.add(6) echo i.len
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
The last element (first from the back) has index
The syntax for indexing is
let j = ['a', 'b', 'c', 'd', 'e'] echo j (1) echo j[^1] (2)
|1||Zero-based indexing: the element at index 1 is
|2||Getting the last element.|
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.
start ..< stop syntax, the
stop index is not included in the slice.
The syntax for slicing is
<container>[<start> .. <stop>].
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.
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 = '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
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.
let n = ("Banana", 2, 'c') (1) echo n
|1||Tuples can contain fields of different types. In this case:
(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.
var o = (name: "Banana", weight: 2, rating: 'c') o = 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')
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.
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
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, 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
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
|2||To return a value from a procedure, we use the
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."
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
This of course means that the name we pass it must be declared as a variable as well, passing in something assigned with
let will throw an error.
While it is good practice to pass things as arguments it is 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
|2||We can also update its value, since it’s declared as a variable.|
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:
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
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:
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
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:
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
|3||First we multiply
true true 20 14
In Nim, every procedure that returns a value has an implicitly declared and initialized (with a default value)
The procedure will return the value of this
result variable when it reaches the end of its indented block, even with no
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
|2||When the end of the procedure is reached, the value of
In older Nim versions (before Nim 0.19.0), the default value of strings and sequences was
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||In Nim version 0.19.0 and newer, this line is not needed — sequences are automatically initialized as empty sequences.
In older Nim versions, sequences must be initialized, and without this line the compiler would throw an error. (Notice that
@[1, 43, 57]
Inside of a procedure we can also call other procedures.
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||Once again, this line is not needed in the newer versions of Nim.|
|2||Calling the previously declared procedure. Its return type is
|3||The third way of calling a procedure, as we saw above.|
@[6, 9, 0, 3] @ @[45390, 3219]
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
|2||Here we define
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
|2||Now we are free to use it in our code, this will work.|
|3||This is were
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.
Create a procedure
findMax3which will return the largest of three values.
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).
Create two procedures
tockwhich 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.
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
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.
import strutils (1) let a = "My string with whitespace." b = '!' echo a.split() (2) echo a.toUpperAscii() (3) echo b.repeat(5) (4)
@["My", "string", "with", "whitespace."] MY STRING WITH WHITESPACE. !!!!!
import math (1) let c = 30.0 # degrees cRadians = c.degToRad() (2) echo cRadians echo sin(cRadians).round(2) (3) echo 2^5 (4)
|2||Converting degrees to radians with
|4||Math module also has
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
secondFile.nim, you can import one from the other as a module:
proc plus*(a, b: int): int = (1) return a + b proc minus(a, b: int): int = (2) return a - b
|1||Notice how the
|2||By contrast this will not be visible when importing this file.|
import firstFile (1) echo plus(5, 10) (2) echo minus(10, 5) # error (3)
|1||Here we import
|2||This will work fine and output
|3||However this will throw an error as the
Interacting with user 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:
Alice A. Bob B. Carol C.
We want to use the contents of that file in our program, as a list (sequence) of names.
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
|2||To split a multiline string into a sequence of strings (each string contains all the contents of a single line) we use
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.
import strutils let contents = readFile("people.txt").strip() (1) echo contents let people = contents.splitLines() echo people
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
echo "Please enter your name:" let name = readLine(stdin) (1) echo "Hello ", name, ", nice to meet you!"
|1||The type of
Please enter your name: (1)
|1||Waiting for user input. After we write our name and press
Please enter your name: Alice Hello Alice, nice to meet you!
VS Code users cannot run this the usual way (using
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.
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
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:
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:
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.
|2||We strip the final new line, and split lines to create a sequence of strings.|
|5||We need to convert the length of a sequence to float, because
Ask a user for their height and weight. Calculate their BMI. Report them the BMI value and the category.
Repeat Collatz conjecture exercise so your program asks a user for a starting number. Print the resulting sequence.
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
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.
If you want to continue learning from Nim tutorials:
If you want to solve some programming puzzles:
The source files are available on Github.