Note from the translatorThis translation was made based on a
comment from the user
stas3k , in which he suggested that
frol translate two parts of the “Nim Tutorial”. I was interested in it and I translated them on my own, to the best of my understanding. If anyone finds mistakes (they probably are there - in the end, the eye has already completely blinked), report in a personal, I will rule.
Introduction
“Der Mensch ist doch ein Augentier - schöne Dinge wünsch ich mir.”
(Quote from the Rammstein group's Morgenstern song. An approximate translation: “But man is a big-eyed beast - I need a lot of beautiful things.”)This is a tutorial on the
Nim programming language
. It is assumed that you are familiar with basic programming concepts, such as variables, types or commands, but deep knowledge is not necessary. A large number of examples of the complex nuances of the language, you can find in the official manual. All code examples in this document follow
the Nim style guide.First program
Let's start with the modified program «hello world»:
Save this code to the
greetings.nim
file. Now compile and run it:
')
nim compile --run greetings.nim
The
--run
causes Nim to automatically run the file after compilation. You can pass arguments to your program via the command line by adding them after the file name:
nim compile --run greetings.nim arg1 arg2
Frequently used commands and keys have abbreviations, so you can write like this:
nim c -r greetings.nim
To compile the release version, use the following command:
nim c -d:release greetings.nim
By default, the Nim compiler generates many runtime checks to simplify debugging. The
-d:release
switch disables these checks and enables optimization.
What the program does should be fairly obvious, but I will explain the syntax: commands written without indentation are executed when the program starts. With indentation, Nim groups commands. Indents are made only by spaces, tabs are not allowed.
String literals are enclosed in double quotes. The
var
command declares a new variable with the name
name
and type
string
, after which it is assigned the value returned by the
readLine
procedure. Because the compiler knows that
readLine
returns a string, you can omit the type in the declaration (this is called local type inference). So this option will also work:
var name = readLine(stdin)
Note that this is almost the only form of type inference that is present in Nim: it is a good compromise between brevity and readability.
The “hello world” program contains some identifiers that are already known to the compiler:
echo
,
readLine
, etc. These built-in commands are declared in the
system
module, which is implicitly imported by any other module.
Lexical elements
Consider the lexical elements of Nim in more detail. Like other programming languages, Nim consists of literals, identifiers, keywords, comments, operators, and punctuation.
String and character literals
String literals are enclosed in double quotes; character - in single. Special characters are escaped with a backslash
\
:
\n
means a line break,
\t
means a tab, and so on. There are also raw string literals:
r"C:\program files\nim"
In raw literals, backslash is not an escape character.
The third and last way to write string literals is
long string literals. They are framed by triple quotes:
""" ... """
, may contain a newline and
\
are not a escape character in them. They are very useful, for example, to include HTML fragments in the code.
Comments
A comment can be anywhere outside of a string or character literal and starts with a
#
. Documenting comments begin with
##
:
The documenting comments are tokens, are included in the syntax tree and, therefore, can only be in certain places in the input file! This allows for easier documentation generators.
You can also use the
discard
command with
long string literals to create block comments:
discard """ Nim - . yes("May I ask a pointless question?") """
Numbers
Numeric literals are written in the same way as in most other languages. To improve readability, it is allowed to beat the digits with underscores:
1_000_000
(one million). Numbers containing a period (or
e
, or
E
) are considered floating point literals:
1.0e9
(one billion). Hexadecimal literals begin with the prefix
0x
, binary ones from
0b
, and octal ones from
0o
. Leading zero
does not make the number octal.
var
command
The
var
command declares a new local or global variable:
var x, y: int
Using the indents after the
var
keyword, you can list the whole variable section:
var x, y: int
Assignment command
The assignment command assigns a new value to a variable or, more generally, a storage location:
var x = "abc"
=
is
an assignment operator. It cannot be overloaded, rewritten or banned, but this may change in future versions of Nim. You can declare several variables with one assignment statement and they all get the same value:
var x, y = 3
Note that declaring several variables with one assignment, which calls a procedure, can give an unexpected result: the compiler
will expand the assignment and eventually call the procedure several times. If the result of the procedure depends on the side effects, your variables can get different values! To avoid this, use only constant values.
Constants
Constants are characters associated with a value. The value of a constant cannot change. The compiler must be able to evaluate the expression in a constant declaration at compile time:
const x = "abc"
Using the indents after the
const
keyword, you can list the whole section of constants:
const x = 1
let
command
The
let
command works something like
var
, but declares
one-time assignment variables
: after initialization, their value cannot be changed.
let x = "abc"
The difference between
let
and
const
following:
let
enter a variable that cannot be reassigned, and
const
means "forcefully compile at compile time and put the result in the data section":
const input = readLine(stdin)
let input = readLine(stdin)
Flow control commands
The welcome program contains three commands that are executed sequentially. But only the most primitive programs can work this way, the more complex ones need more cycles and branching.
if
command
The
if
command is one of the ways to organize the execution thread branch:
let name = readLine(stdin) if name == "": echo("Poor soul, you lost your name?") elif name == "name": echo("Very funny, your name is name.") else: echo("Hi, ", name, "!")
Elif branches can be zero or more, the
else
branch is not required. The
elif
keyword is an abbreviation for
else if
, so as not to make unnecessary indents. (
""
is an empty string, it does not contain characters.)
case
command
Another branching method is implemented by the
case
command. It divides the execution thread into several branches:
let name = readLine(stdin) case name of "": echo("Poor soul, you lost your name?") of "name": echo("Very funny, your name is name.") of "Dave", "Frank": echo("Cool name!") else: echo("Hi, ", name, "!")
As you can see, a comma-separated list of values can be used as an argument for
of
.
The
case
command can work with integers, other enumerated types and strings. (What enumerated types will be discussed later.) For integer numbers and enumerated types, you can use value ranges:
However, the code above will not compile. The reason is that you need to cover
all the values that
n
can take, and the code only processes
0..8
values. Since it is not too practical to list all possible integers (although this is possible due to the notation of ranges), we will correct this by telling the compiler that for all other values you don’t need to do anything:
... case n of 0..2, 4..7: echo("The number is in the set: {0, 1, 2, 4, 5, 6, 7}") of 3, 8: echo("The number is 3 or 8") else: discard
An empty
discard
command is a command to
do nothing. The compiler knows that the
case
command with the
else
section covers all possible options and, thus, the error disappears. Please note that it is impossible to cover all string values: this is why in the case of strings, the
else
branch is required.
In general, the
case
command is used for ranges of types or enumerations, where it helps that the compiler checks the coverage of all possible values.
while
command
The
while
command is a simple loop:
echo("What's your name? ") var name = readLine(stdin) while name == "": echo("Please tell me your name: ") name = readLine(stdin)
In the example, the
while
used to ask the user for his name until he presses ENTER (that is, he does not enter an empty string).
For command
The
for
command implements a loop through all the elements of the iterator. Here is an example of using the built-in
countup
iterator:
echo(" : ") for i in countup(1, 10): echo($i)
The built-in operator
$
converts an integer (
int
) and many other types to a string. The variable
i
implicitly declared as a
for
loop and is of type
int
, since
countup
returns this type.
i
passes by the values 1, 2, .., 10. Each value is output using
echo
. This code does the same thing:
echo(" 10: ") var i = 1 while i <= 10: echo($i) inc(i)
The countdown is implemented as simply (but not so often needed):
echo(" 10 1: ") for i in countdown(10, 1): echo($i)
Since counting with increasing is often used in programs, Nim has an iterator
..
that does the same thing as
countup
:
for i in 1..10: ...
Scopes and the block
command
Flow control commands have a feature that has not yet been discussed: they open up a new scope. This means that in the following example,
x
not available outside the loop:
while false: var x = "hi" echo(x)
The
while
(
for
) command creates an implicit block. Identifiers are visible only inside the block in which they were declared. The
block
command can be used to open a new block explicitly:
block myblock: var x = "hi" echo(x)
The block
label (
myblock
in the example) is optional.
break
command
You can exit the block ahead of time with the
break
command. This command can interrupt a block of
while
,
for
or
block
commands. It exits the nearest block if the block label is not set, from which you need to exit:
block myblock: echo(" ") while true: echo("") break
continue
command
As in many other programming languages, the
continue
command immediately proceeds to the next iteration:
while true: let x = readLine(stdin) if x == "": continue echo(x)
when
command
Example:
when system.hostOS == "windows": echo("running on Windows!") elif system.hostOS == "linux": echo("running on Linux!") elif system.hostOS == "macosx": echo("running on Mac OS X!") else: echo("unknown operating system")
The
when
command is almost identical to the
if
command, but there are some differences:
- each condition must be a constant expression, since it is computed by the compiler;
- commands inside a branch do not open a new scope;
- the compiler checks the syntax and generates code only for commands belonging to the branch according to the first condition, which returns
true
.
The
when
command is useful for writing platform-specific code, by analogy with the
#ifdef
language's
#ifdef
construct.
Note : To comment out a large piece of code, it is often more convenient to use the when false:
construct instead of comments. It can be made repeatedly invested.
Commands and indents
Now that we’ve covered the basic flow control commands, let's go back to the Nim indent rules.
In Nim there is a distinction between
simple and
complex commands. Simple commands, such as assignment, procedure call, or
return
command, cannot contain other commands.
Complex commands such as
if
,
when
,
for
,
while
may contain other commands. To avoid ambiguity, complex commands are always indented, but simple ones are not:
Parts of commands that usually result in some value are called
expressions. To improve readability, they may contain indents in certain places:
if thisIsaLongCondition() and thisIsAnotherLongCondition(1, 2, 3, 4): x = true
In short, indents in expressions are allowed after statements, after opening parentheses and after commas.
Using parentheses and semicolons (
;
) you can use commands where only expressions are allowed:
Procedures
To create new commands, such as
echo
and
readLine
from the examples, we need the concept of
procedures. (In some languages, they are called
methods or
functions.) In Nim, new procedures are defined using the
proc
keyword:
proc yes(question: string): bool = echo(question, " (y/n)") while true: case readLine(stdin) of "y", "Y", "yes", "Yes": return true of "n", "N", "no", "No": return false else: echo("Please be clear: yes or no") if yes("Should I delete all your important files?"): echo("I'm sorry Dave, I'm afraid I can't do that.") else: echo("I think you know what the problem is just as well as I do.")
This example shows a procedure called
yes
, which asks the user a question and returns
true
if he answered “yes”, and
false
if he answered “no”. The
return
command causes an immediate exit from the procedure (and, accordingly, a
while
). Syntax
(question: string): bool
means that the procedure expects to receive a parameter named
question
and type
string
and returns a value of type
bool
.
bool
is a built-in type: the only values it can take are
true
and
false
. Conditions in
if
or
while
commands must be of type
bool
.
A bit of terminology: in the example, the
question
formally called a
parameter, and
"Should I..."
is the
argument that is passed in this parameter.
Result variable
In any procedure that returns a value, the
result
variable is implicitly declared, which is the return value. The
return
command with no arguments is just a shorthand for
return result
. The
result
variable is always returned when exiting the procedure, even if there was no
return
command.
proc sumTillNegative(x: varargs[int]): int = for i in x: if i < 0: return result = result + i echo sumTillNegative()
At the time the function starts, the
result
variable is always already declared, so trying to declare it again, for example, using
var result
, will cause its normal variable to be shaded with the same name. The variable
result
always initialized with the default value for its type. Therefore, the reference data types will have a value of
nil
, so if necessary they will have to be initialized manually.
Options
In the procedure body, the parameters are constants. Since they cannot be changed by default, this allows the compiler to realize the transfer of parameters in the most efficient way. If a variable variable is needed inside a procedure, it should be declared in the body of the procedure using
var
. Parameter name shading is possible and sometimes used:
proc printSeq(s: seq, nprinted: int = -1) = var nprinted = if nprinted == -1: s.len else: min(nprinted, s.len) for i in 0 .. <nprinted: echo s[i]
If the procedure needs to modify the argument to pass to the caller, you can use the
var
parameter:
proc divmod(a, b: int; res, remainder: var int) = res = a div b
In the example,
res
and
remainder
are
var
parameters. Such parameters can be modified by the procedure and the changes will be visible to the caller. Note that in the example above, instead of the
var
parameters, it would be better to return a tuple.
discard
command
To call a procedure that returns a value and ignore the result of calling it,
you must use the
discard
command. In Nim, you cannot simply take and drop the returned value:
discard yes(" ?")
The returned value can be ignored implicitly if the callable procedure or iterator was declared with the discardable
discardable
:
proc p(x, y: int): int {.discardable.} = return x + y p(3, 4)
The
discard
command can also be used to create a block of comments, as described in the
Comments section.
Named Arguments
It happens that the procedure has many parameters and it is difficult to remember in what order they go. This is especially true for procedures that construct complex data types. In such cases, the procedure arguments can be named to make it clearer which argument corresponds to which parameter:
proc createWindow(x, y, width, height: int; title: string; show: bool): Window = ... var w = createWindow(show = true, title = "My Application", x = 0, y = 0, height = 600, width = 800)
Now that we have used named arguments to call
createWindow
, the order of the arguments no longer matters. Named arguments can be mixed with unnamed ones, but this degrades readability:
var w = createWindow(0, 0, title = "My Application", height = 600, width = 800, true)
The compiler checks that each parameter takes exactly one argument.
Default values
To make
createWindow
easier to use, it must provide
default values, that is, those values that will be used as arguments, if the caller does not specify them:
proc createWindow(x = 0, y = 0, width = 500, height = 700, title = "unknown", show = true): Window = ... var w = createWindow(title = "My Application", height = 600, width = 800)
Now, when calling
createWindow
you need to specify only those values that differ from the default values.
Note that for parameters with default values, type inference works, so there is no need to write, for example,
title: string = "unknown"
.
Overloaded procedures
Nim makes it possible to overload procedures by analogy with C ++:
proc toString(x: int): string = ... proc toString(x: bool): string = if x: result = "true" else: result = "false" echo(toString(13))
(Remember that Nim's
toString
is usually implemented with the
$
operator.) The compiler will choose the most appropriate procedure for calling
toString
. How exactly the algorithm for selecting overloaded procedures works will not be discussed here (this issue will soon be dealt with in the manual). However, it does not lead to unpleasant surprises and is based on a rather simple unification algorithm. Ambiguous calls result in an error message.
Operators
The Nim library makes heavy use of overloading - one of the reasons for this is that every operator like
+
is just an overloaded procedure. Parser allows you to use operators in
infix (a + b)
or
prefix notation (+ a)
. The infix operator always takes two arguments, and the prefix operator always takes one. Postfix operators are forbidden, since they can lead to ambiguity:
a @ @ b
means
(a) @ (@b)
or
(a@) @ (b)
? Since there are no postfix operators in Nim, this expression will always mean
(a) @ (@b)
.
In addition to several built-in keyword statements, such as
and
,
or
and
not
, operators always consist of the following characters:
+ - * \ / < > = @ $ ~ & % ! ? ^ . |
+ - * \ / < > = @ $ ~ & % ! ? ^ . |
User-defined operators are allowed. Nothing prevents you from defining your own
@!?+~
Operator, but readability may suffer.
The operator’s priority is determined by its first character.
Details can be found in the manual.To define an operator, enclose it in apostrophes: proc `$` (x: myDataType): string = ...
This notation can also be used to call an operator as a procedure: if `==`( `+`(3, 4), 7): echo("True")
Advance announcements
Each variable, procedure, etc. must be declared before it can be used. (The reason is that it is difficult to come up with a better solution for a language that supports metaprogramming to the same extent that Nim supports it.) However, this cannot be done in mutually recursive procedures:
It odd
depends on even
and vice versa. Thus, the even
compiler must meet before it is fully defined. The syntax for such a preliminary declaration is simple: just skip the =
body of the procedure. assert
adds boundary conditions and will be described later in the Modules section .In future versions of the language, the requirements for preliminary announcements will be less stringent.The example also shows how the body of an operator can consist of a single expression whose value is returned implicitly.Iterators
Let's go back to the boring example with the calculation: echo(" : ") for i in countup(1, 10): echo($i)
Is it possible to write a procedure countup
for use in such a loop? Let's try:
proc countup(a, b: int): int = var res = a while res <= b: return res inc(res)
, . , ,
.
« » yield
.
proc
iterator
– :
iterator countup(a, b: int): int = var res = a while res <= b: yield res inc(res)
, :
for
;return
, yield
;result
;- ;
- iterators cannot be declared previously, since the compiler must be able to inline (inline) an iterator (this restriction will be removed in future versions of the compiler).
However, you can also use an iterator closure
that has a different set of constraints. For details, see the first class iterators documentation . Iterators can have the same names and parameters as procedures: they have their own namespace. Therefore, there is a common practice for wrapping iterators into procedures with the same names that accumulate the result of an iterator and return it as a sequence, as split
from a module strutils
.Base types
This section describes in detail the basic built-in types and operations that are available to them.Boolean values
The logical type in Nim is called bool
and consists of two predefined values true
and false
. Conditions in the commands while
, if
, elif
and when
must have a type bool
.For type bool
defined operators not
, and
, or
, xor
, <
, <=
, >
, >=
, !=
and ==
. Operators and
and or
perform abbreviated calculations. Example:
while p != nil and p.name != "xyz":
Characters
The character type in Nim is called char
. Its size is one byte. Thus, it cannot be a UTF-8 character, only part of it. The reason for this is efficiency: in the overwhelming majority of cases, ready-made programs will correctly process data in UTF-8, since UTF-8 was designed specifically for this. Character literals are enclosed in single quotes.Symbols can be compared using operators ==
, <
, <=
, >
and >=
. The operator $
converts char
to string
. Characters cannot be mixed with integers; To obtain the numerical value of a symbol, use the procedure ord
. Conversion from a numerical value to a symbol is performed using a procedure chr
.Strings
String values in Nim are mutable , so the operation of adding a substring to a string is quite efficient. Lines in Nim end with zero at the same time and contain a length field. The length of the string can be obtained by an inline procedure len
; length never takes into account the terminating zero. Access to the trailing zero does not cause an error and often leads to simplification of the code: if s[i] == 'a' and s[i+1] == 'b':
The assignment operator for strings copies the string. You can use the operator &
to concatenate strings and add
to add a substring.String comparisons are done in lexicographical order. Any comparison operators are allowed. By convention, all strings are UTF-8 strings, but this is not necessary. For example, when reading lines from binary files, they are rather a sequence of bytes. Operation s[i]
means the i
-th character (and not the i
-th unicode character) of the string s
.String variables are initialized by a special value called nil
. However, most string operations cannot work withnil
(this raises an exception) for performance reasons. Instead nil
of an empty value, use an empty string ""
. But it ""
creates a string object in the heap, so here you need to look for a compromise between memory and performance.Whole numbers
Nim has the following built-in integer types: int
, int8
, int16
, int32
, int64
, uint
, uint8
, uint16
, uint32
and uint64
.The default is an integer type int
. Integer literals can have a suffix denoting their belonging to one or another integer type: let x = 0
, ,
int
.
+
,
-
,
*
,
div
,
mod
,
<
,
<=
,
==
,
!=
,
>
>=
.
and
,
or
,
xor
not
.
shl
, –
shr
. . .
, .
, . , ,
EOutOfRange
( ).
Nim :
float
,
float32
float64
.
float
.
float
64 .
, :
var x = 0.0
Major operators +
, -
, *
, /
, <
, <=
, ==
, !=
, >
and >=
defined for floating point numbers and correspond to the IEEE standard.Automatic type conversion in expressions with different types of floating point types is performed: smaller types are converted to larger ones. Integer types are not automatically converted to floating point types and vice versa. For such transformations, the toInt
and procedures can be used toFloat
.Type conversion
Conversion between base types in Nim is done by using the type as a function: var x: int32 = 1.int32
Internal Type Representation
,
$
,
echo
.
$
, , .
,
$
.
repr
, . ,
$
repr
:
var myBool = true myCharacter = 'n' myString = "nim" myInteger = 42 myFloat = 3.14 echo($myBool, ":", repr(myBool))
New types in Nim can be defined using the command type
: type biggestInt = int64
Enumerations and object types cannot be defined on the fly, only in a command type
.Transfers
A variable relating to the type of enumeration may take only a limited set of values. This set consists of ordered characters. Each character is internally mapped to an integer value. The first character corresponds to the number 0, the second to the number 1, and so on. Example:
type Direction = enum north, east, south, west var x = south
For enumerations can be used any comparison operators.For the avoidance of transfers symbols can be classified: Direction.south
.The operator $
can convert any value of the enumeration into its name, and the procedure ord
into the corresponding integer value.For better interaction with other programming languages, integer values can be explicitly assigned to enumeration characters. However, in any case, they should be in ascending order. A character that has not been explicitly assigned a numerical value is assigned the value of the previous character plus 1.Explicitly numbered enumerations may contain omissions: type MyEnum = enum a = 2, b = 4, c = 89
Enumerated types
Transfers without breaks, integer types char
, bool
(and their sub-bands) - they are called enumerated types. For enumerated types, there are several special operations:Operation | Comment |
---|
ord(x) | returns an integer that is used to represent the value x |
inc(x) | increases x by 1 |
inc(x, n) | increases x by n ; n - integer |
dec(x) | decreases x by 1 |
dec(x, n) | decreases x by n ; n - integer |
succ(x) | returns the next x item |
succ(x, n) | returns the n th item followingx |
pred(x) | returns the predecessor x |
pred(x, n) | returns n th th predecessorx |
Operation inc
, dec
, succ
and pred
can be executed with an error, throws an exception EOutOfRange
or EOverflow
. (Unless, of course, the code is compiled with exceptions checks enabled.)Ranges
This type is a range of values of an integer type or enumeration (base type). Example:
type Subrange = range[0..5]
Subrange
this is a range int
that can contain values from 0 to 5. Assigning any other values to a type variable Subrange
will result in a compilation error or runtime. The assignment of a base type to one of its ranges (and vice versa) is permitted.The module system
defines an important type Natural
as range[0..high(int)]
( high
returns the maximum allowed value). Other programming languages force unsigned integers to use natural numbers. This is often wrong : you should not be forced to use unsigned arithmetic only for the reason that numbers cannot be negative. The Natural
Nim language type avoids this common programming error.The sets
set
. , :
int8-int16
uint8/byte-uint16
char
enum
. , . :
var s: set[int64]
:
{}
. . ( ):
type CharSet = set[char] var x: CharSet x = {'a'..'z', '0'..'9'}
:
Operation | Description |
---|
A + B | |
A * B | |
A - B | ( A B ) |
A == B | |
A <= B | subset ratio ( A is a subset B or equivalent B ) |
A < B | strict subset relation ( A is a subset B ) |
e in A | belonging to a set ( A contains an element e ) |
e notin A | A does not contain an element e |
contains(A, e) | A contains an element e |
card(A) | power A (number of elements in A ) |
incl(A, elem) | the same as A = A + {elem} |
excl(A, elem) | the same as A = A - {elem} |
Sets are often used for procedure flags. This is a more transparent (and type-safe) solution than the definition of integer constants that need to be combined with an operation or
.Arrays
An array is a simple fixed-size container. All its elements are of the same type. As an array index, any enumerated type can be used.An array can be created using []
: type IntArray = array[0..5, int]
Notation is x[i]
used to gain access to the i
-th element x
. When accessing the elements of an array, the boundaries are always checked (either at compile time or at run time). This check can be disabled by pragmas or by calling the compiler with the key --bound_checks:off
.Arrays are value types, like other Nim types. The assignment statement copies the contents of the entire array.The built-in procedure len
returns the length of the array. low(a)
returns the smallest possible array index a
, and the high(a)
largest possible index. type Direction = enum north, east, south, west BlinkLights = enum off, on, slowBlink, mediumBlink, fastBlink LevelSetting = array[north..west, BlinkLights] var level: LevelSetting level[north] = on level[south] = slowBlink level[east] = fastBlink echo repr(level)
The syntax for nested arrays (plural dimension) in other languages is to add additional square brackets, since usually each dimension must be of the same type as the others. In Nim, you can use different dimensions with different index types, so the nesting syntax is somewhat different. Based on the previous example, where level
defined as an array of enumerations indexed by another enum, we can add the following lines to allow the type of beacon to be divided into levels that can be accessed through their integer index: type LightTower = array[1..10, LevelSetting] var tower: LightTower tower[1][north] = slowBlink tower[1][east] = mediumBlink echo len(tower)
Note that the built-in procedure len
returns the length of the first level array only. To even better show the nested nature LightTower
, one would not have to write the previous type definition LevelSetting
, but instead include it directly in the first dimension type: type LightTower = array[1..10, array[north..west, BlinkLights]]
Quite often, arrays start from zero, so there is an abbreviated syntax for specifying a range from zero to the specified index minus one: type IntArray = array[0..5, int]
Sequences
Sequences are like arrays, only their length can change during execution (as with strings). Since sequences can be resized, they are always heaped and participate in garbage collection.Sequences are always indexed int
, starting with 0. Operations len
, low
and high
applicable to sequences. Notation x[i]
can be used to access the i
-th element x
.Sequences can be constructed using an array constructor []
connected to an array-to-sequence conversion operator @
. Another way to allocate memory for a sequence is to call an inline procedure newSeq
.A sequence can be passed in a parameter openarray
.Example:
var x: seq[int]
Sequence variables are initialized by value nil
. However, most sequence operations cannot work with nil
(this will raise an exception) for reasons related to performance. So as an empty value, it is desirable to use an empty sequence @[]
, and not nil
. But it @[]
creates a sequence object in the heap, so you will need to look for a solution that is acceptable for your particular case.The command for
used for the sequence can work with one or two variables. If you use a single variable form, the variable will contain the value provided by the sequence. The team for
runs on the results obtained from the items()
module iteratorsystem
. , , – .
for
pairs()
system
. Examples:
for i in @[3, 4, 5]: echo($i)
: .
, : . . 0.
len
,
low
high
. , .
var fruits: seq[string]
: , .
varargs
. , . :
proc myWriteln(f: File, a: varargs[string]) = for s in items(a): write(f, s) write(f, "\n") myWriteln(stdout, "abc", "def", "xyz")
,
varargs
. :
proc myWriteln(f: File, a: varargs[string, `$`]) = for s in items(a): write(f, s) write(f, "\n") myWriteln(stdout, 123, "abc", 4.0)
$
,
a
. ,
$
, , .
Syntax slices look similar to range types, but are used in a different context. A slice is simply an object of type Slice
that contains two borders, a
and b
. The slice itself is not very useful, but other types of collections define operators that accept objects Slice
to specify ranges. var a = "Nim is a progamming language" b = "Slices are useless." echo a[7..12]
In the previous example, slices are used to modify a fragment of a string. The slice boundaries can contain any value that is supported by their type, but only the procedure using the slice object determines whether it will accept this value.Tuples (tuples)
.
()
. . , .
.
t.field
. ,
t[i]
i
- (
i
).
type Person = tuple[name: string, age: int]
Although you do not need to declare a type to use a tuple, tuples created with different field names will be considered different objects, even if they have the same field types.Tuples can be unpacked in the process of assigning variables (and only in this case!). This is useful to directly assign the values of the tuple fields to individual named variables. As an example, consider a procedure splitFile
from a module os
that simultaneously returns a directory, a name, and a path extension. To properly unpack a tuple, you need to use parentheses around the values into which you unpack the tuple, otherwise you will assign the same value to each of these variables! Example:
import os let path = "usr/local/nimc.html" (dir, name, ext) = splitFile(path) baddir, badname, badext = splitFile(path) echo dir
Unpacking tuples works only in blocks var
or let
. The following code will not compile: import os var path = "usr/local/nimc.html" dir, name, ext = "" (dir, name, ext) = splitFile(path)
Reference types and pointers
References (the same as pointers in other programming languages) are a way to organize many-to-one relationships. This means that different links can point to the same place in memory and modify it.Nim distinguishes between traced and untraced links. Untracked links are also called pointers. Tracked links point to objects on the heap with garbage collection, untracked - on objects for which memory was allocated manually or on objects in other places of memory. Thus, untracked links are unsafe. However, for some low-level operations (access to the "hardware") can not do without them.Tracked links are declared by the keyword ref
, and untraceable - by the keyword ptr
.Empty subscript-notation []
can be used to dereference a link, that is, to get the element that the link points to. Operators .
(access to a tuple / object field) and []
(array / string / sequence index operator) perform implicit dereferencing for reference types: type Node = ref NodeObj NodeObj = object le, ri: Node data: int var n: Node new(n) n.data = 9
To allocate memory for a new monitored object, a built-in procedure is used new
. To work with untraceable memory, procedures can be used alloc
, dealloc
and realloc
. Module documentation system
contains additional information on these issues.If the link does not indicate anything, it matters nil
.Procedural type
A procedural type is (somewhat abstract) a pointer to a procedure. Variables of a procedural type may have a value nil
. Nim uses a procedural type to implement functional programming techniques.Example:
proc echoItem(x: int) = echo(x) proc forEach(action: proc (x: int)) = const data = [2, 3, 5, 7, 11] for d in items(data): action(d) forEach(echoItem)
A non-obvious problem with procedural types is that procedure call conventions affect type compatibility: procedural types are compatible only when they use the same calling conventions. Various calling conventions are listed in the manual .Modules
Nim supports program partitioning according to the modular concept. Each module is in a separate file. Modules allow information hiding and separate compilation. They can access the symbols of other modules using the command import
. Only top-level characters marked with an asterisk ( *
) can be exported :
The module A
exports x
and *
, but not y
.Top-level commands in the module are executed when the program starts. This can be used, for example, to initialize complex data structures.Each module has a special magic constant isMainModule
, which is true if the module is compiled as the main file. This is very useful for embedding inside the test module, as shown in the previous example.Modules dependent on other modules are allowed, but very undesirable, because in this case the module cannot be reused without its dependencies.The algorithm for compiling modules is as follows:- Compile the module as usual, recursively following the commands
import
. - If a loop is detected, import only already parsed characters (exported); if an unknown identifier is encountered, then abort the operation.
This is best shown by example:
Module characters can be qualified using syntax module.symbol
. If the character is ambiguous, it must qualify. A symbol is ambiguous if it is defined in two (or more) different modules and both modules are imported by the third:
But this rule does not apply to procedures or iterators. Overload rules apply here:
Character exclusion
Usually, the command import
takes all exported characters. This can be changed by specifying exclude characters with qualifier except
. import mymodule except y
From team
import
, .
from import
:
from mymodule import x, y, z
from
, , .
from mymodule import x, y, z x()
from mymodule import nil mymodule.x()
, , .
from mymodule as m import nil mx()
include
include
, : . :
include fileA, fileB, fileC
( : « Nim ( 2)» .)