Collection of Nim snippets with brief notes as I try them out from the official Nim tutorial , Nim by Example and many other places. This also includes my own musings and hard-learned lessons.

Updated for Nim 1.1.0 Updated for Nim 0.20.0 No need to import typetraits when just printing the type name, re-eval all code snippets using Nim devel as of today Overhaul all Nim doc links to point to the Devel docs version Verified all the code snippets for Nim 0.19.0

Nim Help #

If using Nim 0.19.0 or newer, use nim --fullhelp — don’t use nim --help .

I ended up with above conclusion as I needed the --out: functionality but couldn’t find it in the default help ( --help ). I don’t see why anyone would ever use the less informative --help over --fullhelp .

If people are complaining about “too much information in help”, they should use it the right way.. pipe the help to grep or rg .

> nim --fullhelp | rg out -o:FILE, --out:FILE set the output filename --stdout output to stdout in the generated output

Nim version #

assert NimVersion is string echo NimVersion assert NimMajor is int echo NimMajor assert NimMinor is int echo NimMinor assert NimPatch is int echo NimPatch

1.1.0 1 1 0

See Check Type ( is ) for is .

Echo with newlines #

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

echo ( "Hello World" )

Hello World

The parentheses are optional.

echo "Hello World"

Hello World

Echo with multiple arguments #

From Nim Docs – echo :

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

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

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

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

100abc2.5 100abc2.5

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

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

Hello World! Hello World again!!

Echo without newlines #

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

HelloWorld

Colored or Styled echo #

Use styledEcho template from the terminal module (which is syntactic sugar for stdout.styledWriteLine from that same module).

The syntax looks like this:

styledEcho <style1>, <style2>, <some string>, resetStyle, <some string>, <style3>, <some other string>, resetStyle

As shown above ( <style1>, <style2>, ), you can stack multiple “styles” together if they make sense.. like styleBright, fgBlue, .

Available Styles #

type Style * = enum ## different styles for text output styleBright = 1 , ## bright text styleDim , ## dim text styleItalic , ## italic (or reverse on terminals not supporting) styleUnderscore , ## underscored text styleBlink , ## blinking/bold text styleBlinkRapid , ## rapid blinking/bold text (not widely supported) styleReverse , ## reverse styleHidden , ## hidden text styleStrikethrough ## strikethrough

Foreground Color #

type ForegroundColor = enum fgBlack = 30 , ## black fgRed , ## red fgGreen , ## green fgYellow , ## yellow fgBlue , ## blue fgMagenta , ## magenta fgCyan , ## cyan fgWhite ## white

Background Color #

type BackgroundColor = enum bgBlack = 40 , ## black bgRed , ## red bgGreen , ## green bgYellow , ## yellow bgBlue , ## blue bgMagenta , ## magenta bgCyan , ## cyan bgWhite ## white

type TerminalCmd = enum resetStyle , ## reset attributes fgColor , ## set foreground's true color bgColor ## set background's true color

styledEcho Example #

import std /[ terminal ] styledEcho "unstyled text" , styleBright , fgGreen , "[PASS]" , resetStyle , fgGreen , " Yay!" styledEcho "unstyled text" , styleBright , fgRed , "[FAIL]" , resetStyle , fgRed , " Nay :("

unstyled text^[[1m^[[32m[PASS]^[[0m^[[32m Yay!^[[0m unstyled text^[[1m^[[31m[FAIL]^[[0m^[[31m Nay :(^[[0m

The binary control char is manually replaced with ASCII “^[” in the above results block and more below. That’s so that they are visible in the Markdown/HTML, and the Org file also remains “ascii-diffable”.

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

Echoing styled text without trailing newline #

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

import std /[ terminal ] stdout . styledWrite ( fgRed , "red text " ) # no newline here stdout . styledWrite ( fgGreen , "green text " ) # no newline here stdout . styledWrite ( fgBlue , "blue text " ) # no newline here stdout . styledWrite ( fgYellow , "yellow text " ) # no newline here stdout . styledWrite ( "ordinary text " ) # no newline here stdout . styledWrite ( fgCyan , "cyan text " ) # no newline here echo ""

Code Snippet 1 stdout.styledWrite to echo styled string without trailing newline : Usingto echo styled string without trailing newline

^[[31mred text ^[[0m^[[32mgreen text ^[[0m^[[34mblue text ^[[0m^[[33myellow text ^[[0mordinary text ^[[36mcyan text ^[[0m

Lower level styling procs/templates #

setForeGroundColor Sets foreground color for the stdout output that follows. It is the same as calling stdout.setForeGroundColor . setBackGroundColor Sets background color for the stdout output that follows. It is the same as calling stdout.setBackGroundColor . resetAttributes Resets the styling/coloring. It is the same as calling stdout.resetAttributes .

import std /[ terminal ] setForeGroundColor ( fgGreen ) echo "green text" echo "more green text" setForeGroundColor ( fgRed ) echo "red text" resetAttributes () echo "ordinary text"

^[[32mgreen text more green text ^[[31mred text ^[[0mordinary text

Below code snippet is similar to Code Snippet 1, except that the low-level procs setForeGroundColor , resetAttributes and stdout.write are used instead of stdout.styledWrite .

import std /[ terminal ] setForeGroundColor ( fgRed ) stdout . write ( "red text " ) # no newline here setForeGroundColor ( fgGreen ) stdout . write ( "green text " ) # no newline here setForeGroundColor ( fgBlue ) stdout . write ( "blue text " ) # no newline here setForeGroundColor ( fgYellow ) stdout . write ( "yellow text " ) # no newline here resetAttributes () stdout . write ( "ordinary text " ) # no newline here setForeGroundColor ( fgCyan ) stdout . write ( "cyan text " ) # no newline here resetAttributes () echo ""

^[[31mred text ^[[32mgreen text ^[[34mblue text ^[[33myellow text ^[[0mordinary text ^[[36mcyan text ^[[0m

Standard Error #

To send message on stderr, use writeLine proc to explicitly write to stderr File.

stderr . writeLine "Error!" quit 1

Above will generate this error:

Error!

To send messages to stderr that don’t end in newlines, use stderr.write instead.

stderr . write "Error1" stderr . write "Error2" quit 1

Above will generate this error:

Error1Error2

DONE When to use stdout.flushFile ? #

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

From the reader Hasan in the comments below:

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

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

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

or

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

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

Why would you want to flush an output buffer? Usually when I do it, it’s because the code is crashing and I’m trying to debug something. The standard buffer will not print everytime you call printf() it waits until it’s full then dumps a bunch at once. So if you’re trying to check if you’re making it to a function call before a crash, it’s helpful to printf something like “got here!“, and sometimes the buffer hasn’t been flushed before the crash happens and you can’t tell how far you’ve really gotten. Another time that it’s helpful, is in multi-process or multi-thread code. Again, the buffer doesn’t always flush on a call to a printf() , so if you want to know the true order of execution of multiple processes you should fflush the buffer after every print. I make a habit to do it, it saves me a lot of headache in debugging. The only downside I can think of to doing so is that printf() is an expensive operation (which is why it doesn’t by default flush the buffer).

Also, in the logging module docs, you see:

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

Line continuation #

Nim does not have any line-continuation character – a character you can use to break a long line of code.

Instead line continuation is inferred if a line ends in an operator, , , or any of these opening brackets ( ( , [ , { ).

See the below code. It works, but it has a really long line of code.

proc isTriangle ( s : openArray [ int ] ): bool = return ( s . len == 3 ) and ( s [ 0 ] <= s [ 1 ] + s [ 2 ] ) and ( s [ 1 ] <= s [ 2 ] + s [ 0 ] ) and ( s [ 2 ] <= s [ 0 ] + s [ 1 ] ) and ( s [ 0 ] > 0 ) and ( s [ 1 ] > 0 ) and ( s [ 2 ] > 0 ) echo isTriangle ( [ 3 , 3 , 3 ] )

true

Below will not work as the continued lines are not ending with an operator – You will get Error: invalid indentation.

I really wish this worked!

proc isTriangle ( s : openArray [ int ] ): bool = return ( s . len == 3 ) and ( s [ 0 ] <= s [ 1 ] + s [ 2 ] ) and ( s [ 1 ] <= s [ 2 ] + s [ 0 ] ) and ( s [ 2 ] <= s [ 0 ] + s [ 1 ] ) and ( s [ 0 ] > 0 ) and ( s [ 1 ] > 0 ) and ( s [ 2 ] > 0 ) echo isTriangle ( [ 3 , 3 , 3 ] )

Below, I am ending the continued lines with the and operator. But that will fail with the same error too.. because I am not indenting the continued lines.

proc isTriangle ( s : openArray [ int ] ): bool = return ( s . len == 3 ) and ( s [ 0 ] <= s [ 1 ] + s [ 2 ] ) and ( s [ 1 ] <= s [ 2 ] + s [ 0 ] ) and ( s [ 2 ] <= s [ 0 ] + s [ 1 ] ) and ( s [ 0 ] > 0 ) and ( s [ 1 ] > 0 ) and ( s [ 2 ] > 0 ) echo isTriangle ( [ 3 , 3 , 3 ] )

Finally, below works because:

The continued lines are ending with an operator ( and in this example).

( in this example). When continued, they resume after an indentation.

proc isTriangle ( s : openArray [ int ] ): bool = return ( s . len == 3 ) and ( s [ 0 ] <= s [ 1 ] + s [ 2 ] ) and ( s [ 1 ] <= s [ 2 ] + s [ 0 ] ) and ( s [ 2 ] <= s [ 0 ] + s [ 1 ] ) and ( s [ 0 ] > 0 ) and ( s [ 1 ] > 0 ) and ( s [ 2 ] > 0 ) echo isTriangle ( [ 3 , 3 , 3 ] )

true

Ref

Templates and Macros #

TODO Templates #

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

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

hello world

DONE Return type of templates #

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

template foo () = 1 echo foo ()

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

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

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

1

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

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

1

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

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

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

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

NEED TO UNDERSTAND Macros #

Comment by @Vindaar from GitHub on fixing the macro here.

from GitHub on fixing the macro here. His further response on why result[^1].add(arg) was used instead of result.add(arg) .

was used instead of . https://flenniken.net/blog/nim-macros/

https://nim-lang.org/blog/2018/06/07/create-a-simple-macro.html

https://hookrace.net/blog/introduction-to-metaprogramming-in-nim/

NEED TO UNDERSTAND Quote Do #

Example of quote do #

Ref by @Vindaar from GitHub from here.

Once I understand Nim macros and quote do , I need to revisit this example to understand the quote do magic. -

import std /[ macros ] type ShapeKind {. pure .} = enum Triangle , Rectangle Shape = object case kind : ShapeKind of Triangle : aT : int bT : int of Rectangle : aR : int bR : int proc calcArea ( s : Shape ) = discard proc createNext ( c : NimNode ): NimNode = let typ = c [ 0 ] let name = c [ 1 ] let a = c [ 2 ][ 0 ][ 0 ] let aVal = c [ 2 ][ 0 ][ 1 ] let b = c [ 2 ][ 1 ][ 0 ] let bVal = c [ 2 ][ 1 ][ 1 ] case $ typ of "Triangle" : a . ident = toNimIdent ( $ a & "T" ) b . ident = toNimIdent ( $ b & "T" ) of "Rectangle" : a . ident = toNimIdent ( $ a & "R" ) b . ident = toNimIdent ( $ b & "R" ) result = quote do : let ` name ` = Shape ( kind : ` typ `, ` a `: ` aVal `, ` b `: ` bVal `) ` name `. calcArea () macro shape ( input : untyped ): untyped = ## should produce ## let aaa = Shape(kind: Triangle, a: 17, b: 23) ## aaa.calcArea() ## let bbb = Shape(kind: Rectangle, a: 5, b: 8) ## bbb.calcArea() result = newStmtList () echo input . treeRepr for i in 0 .. < input . len : let c = input [ i ] case c . kind of nnkCommand : result . add createNext ( c ) else : echo "Needs to be a Command!" echo result . repr shape : Triangle aaa : a = 17 b = 23 Rectangle bbb : a = 5 b = 8

TODO Quote Helper #

Nim Docs – Macros / quote

TODO Term Rewriting Macros #

Nim Manual – Term Rewriting Macros

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

import std /[ strformat ] template optMul {` * `( a , 2 )}( a : int ): int = debugEcho ( "-> Term rewriting activated!" ) a + a echo & "First arg = 3 ({ $type (3)})" echo "Calculating 3 * 2:" # Why isn't term rewriting activated here? echo 3 * 2 let x = 3 echo "" echo & "First arg = x ({ $type (x)})" echo "Calculating x * 2:" echo x * 2 echo "" echo & "First arg = 2 ({ $type (2)})" echo "Calculating 2 * x:" echo 2 * x

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

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

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

TO BE FIXED TRM Side Effects #

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

template optMul {` * `( a , 2 )}( a : int ): int = debugEcho ( "-> Term rewriting activated!" ) a + a proc f (): int = echo "side effect!" result = 55 echo f () * 2

-> Term rewriting activated! side effect! side effect! 110

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

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

Though, as of , the noSideEffect seems to have no .. effect. See Nim Issue #6217.

template optMul {` * `( a , 2 )}( a : int { noSideEffect }): int = debugEcho ( "-> Term rewriting activated!" ) a + a proc f (): int = echo "side effect!" result = 55 echo f () * 2 # not optimized ;-)

-> Term rewriting activated! side effect! side effect! 110

TO BE FIXED TRM Commutative #

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

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

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

template mulIsCommutative {` * `( a , b )}( a , b : int ): int = debugEcho ( "-> Term rewriting activated!" ) b * a let x = 3 echo "Calculating x * 2:" echo x * 2 echo "" echo "Calculating x * 2 again:" echo x * 2 echo "" echo "Calculating x * 3:" echo x * 3

Calculating x * 2: -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! -> Term rewriting activated! 6 Calculating x * 2 again: 6 Calculating x * 3: 9

TRM Parameter Constraints #

The Nim Manual says:

What optimizers really need to do is a canonicalization.

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

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

template canonMul {` * `( a , b )}( a : int { lit }, b : int ): int = debugEcho ( "-> Term rewriting activated!" ) b * a let x = 3 y = 4 echo "The TRM won't be activated for 'x * y' because 'x' is not a literal int:" echo x * y echo "" echo "The TRM will activated for '3 * y' because '3' *is* a literal int:" echo 3 * y

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

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

lit is just one of the many available TRM parameter constraints!

Representing one type in another #

Table 1 : Representing one type in another From Type / To Type bool char int float string bool - N/A int float $ or strformat.fmt or use plain logic to return string char N/A - ord (or int ) float $ or strformat.fmt or custom logic using newString int float.bool (or bool , int needs to be 0 or 1) float.char (or chr or char , int needs to be in [0,255]) - float $ or strformat.fmt or strutils.intToStr float bool char (truncation + rollover) int (truncation + rollover) - $ or strformat.fmt string strutils.parseBool as a seq of char strutils.parseInt strutils.parseFloat -

From bool #

bool to char #

not applicable

DONE bool to int #

import std /[ strformat ] let b_seq = @[ true , false ] for b in b_seq : var b_int = b . int echo & "Value of { $type (b)} b is {b}" echo & "Value of { $type (b_int)} b_int is {b_int}"

Value of bool b is true Value of int b_int is 1 Value of bool b is false Value of int b_int is 0

DONE bool to float #

import std /[ strformat ] let b_seq = @[ true , false ] for b in b_seq : var b_float = b . float echo & "Value of { $type (b)} b is {b}" echo & "Value of { $type (b_float)} b_float is {b_float}"

Value of bool b is true Value of float b_float is 1.0 Value of bool b is false Value of float b_float is 0.0

DONE bool to string #

import std /[ strformat ] let b_seq = @[ true , false ] for b in b_seq : var b_string1 = $ b var b_string2 = & "{b}" var b_string3 = if b : "true" else : "false" echo & "Value of { $type (b)} b is {b}" echo & " Value of { $type (b_string1)} b_string1 is {b_string1}" echo & " Value of { $type (b_string2)} b_string2 is {b_string2}" echo & " Value of { $type (b_string3)} b_string3 is {b_string3}"

Value of bool b is true Value of string b_string1 is true Value of string b_string2 is true Value of string b_string3 is true Value of bool b is false Value of string b_string1 is false Value of string b_string2 is false Value of string b_string3 is false

From char #

char to bool #

not applicable

DONE char to int #

import std /[ strformat ] let c = 'A' c_int1 = c . ord c_int2 = c . int echo & "Value of { $type (c)} c is {repr(c)}" echo & " Value of { $type (c_int1)} c_int1 is {c_int1}" echo & " Value of { $type (c_int2)} c_int2 is {c_int2}"

Value of char c is 'A' Value of int c_int1 is 65 Value of int c_int2 is 65

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

DONE char to float #

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

Value of char c is 'A' Value of float c_float is 65.0

DONE char to string #

import std /[ strformat , strutils ] let c_seq = @[ 'A' , 'b' , '@' ] for c in c_seq : let c_string1 = $ c c_string2 = & "{c}" c_string3 = @[ c ] . join ( "" ) var c_string4 = newString ( 1 ) c_string4 [ 0 ] = c echo & "Value of { $type (c)} c is {repr(c)}" echo & " Value of { $type (c_string1)} c_string1 is {c_string1}" echo & " Value of { $type (c_string2)} c_string2 is {c_string2}" echo & " Value of { $type (c_string3)} c_string3 is {c_string3}" echo & " Value of { $type (c_string4)} c_string4 is {c_string4}"

Value of char c is 'A' Value of string c_string1 is A Value of string c_string2 is A Value of string c_string3 is A Value of string c_string4 is A Value of char c is 'b' Value of string c_string1 is b Value of string c_string2 is b Value of string c_string3 is b Value of string c_string4 is b Value of char c is '@' Value of string c_string1 is @ Value of string c_string2 is @ Value of string c_string3 is @ Value of string c_string4 is @

Join a sequence of characters into a string #

import std /[ strformat , strutils ] let c_seq = @[ 'a' , 'b' , 'c' , 'd' ] str = c_seq . join ( "" ) echo & "{str} is the stringified form of { $type (c_seq)} {c_seq}"

abcd is the stringified form of seq[char] @['a', 'b', 'c', 'd']

From int #

DONE int to bool #

The int value needs to be either 0 or 1.

RangeError exception is thrown for any other int value.

import std /[ strformat ] let i_seq = @[- 1 , 0 , 1 , 2 ] for i in i_seq : echo & "Value of { $type (i)} i is {i}" var i_bool : bool try : i_bool = i . bool echo & " Value of { $type (i_bool)} i_bool is {i_bool}" except : echo & " [Error] {getCurrentException().name}: {getCurrentException().msg}"

Value of int i is -1 [Error] RangeError: value out of range: -1 Value of int i is 0 Value of bool i_bool is false Value of int i is 1 Value of bool i_bool is true Value of int i is 2 [Error] RangeError: value out of range: 2

One way to get around this limitation on int values is to first convert it to a float and then to a bool . With this, any non-zero value of the int will return true , and only the value of 0 will return false .

import std /[ strformat ] let i_seq = @[- 1000 , 0 , 1 , 1000 ] for i in i_seq : echo & "Value of { $type (i)} i is {i}" var i_bool : bool try : i_bool = i . float . bool echo & " Value of { $type (i_bool)} i_bool is {i_bool}" except : echo & " [Error] {getCurrentException().name}: {getCurrentException().msg}"

Value of int i is -1000 Value of bool i_bool is true Value of int i is 0 Value of bool i_bool is false Value of int i is 1 Value of bool i_bool is true Value of int i is 1000 Value of bool i_bool is true

DONE int to char #

The int value needs to be in the [0, 255] range.

range. RangeError exception is thrown if that value is outside that range.

import std /[ strformat ] let i_seq : seq [ int ] = @[- 1 , 0 , 62 , 256 ] for i in i_seq : var i_char1 , i_char2 : char echo & "Value of { $type (i)} i is {i}" try : i_char1 = chr ( i ) # or i.chr echo & " Value of { $type (i_char1)} i_char1 is {repr(i_char1)}" except : echo & " [Error] {getCurrentException().name}: {getCurrentException().msg}" try : i_char2 = i . char # or char(i) echo & " Value of { $type (i_char2)} i_char2 is {repr(i_char2)}" except : echo & " [Error] {getCurrentException().name}: {getCurrentException().msg}"

Value of int i is -1 [Error] RangeError: value out of range: -1 [Error] RangeError: value out of range: -1 Value of int i is 0 Value of char i_char1 is '\0' Value of char i_char2 is '\0' Value of int i is 62 Value of char i_char1 is '>' Value of char i_char2 is '>' Value of int i is 256 [Error] RangeError: value out of range: 256 [Error] RangeError: value out of range: 256

chr converts only an 8-bit unsigned integer ( range[0 .. 255] ).

One way to get around this limitation on int values is to first convert it to a float and then to a char . With this, any value of int < 0 or >= 256 will returned a rolled-over char value.

import std /[ strformat ] let i_seq = @[- 2 , - 1 , 0 , 1 , 254 , 255 , 256 , 257 ] for i in i_seq : var i_char = i . float . char echo & "Value of { $type (i)} i is {i}" echo & " Value of { $type (i_char)} i_char is {repr(i_char)}"

Value of int i is -2 Value of char i_char is '\254' Value of int i is -1 Value of char i_char is '\255' Value of int i is 0 Value of char i_char is '\0' Value of int i is 1 Value of char i_char is '\1' Value of int i is 254 Value of char i_char is '\254' Value of int i is 255 Value of char i_char is '\255' Value of int i is 256 Value of char i_char is '\0' Value of int i is 257 Value of char i_char is '\1'

DONE int to float #

import std /[ strformat ] let i_seq : seq [ int ] = @[- 1 , 0 , 1000 ] for i in i_seq : var i_float = i . float echo & "Value of { $type (i)} i is {i}" echo & "Value of { $type (i_float)} i_float is {i_float}"

Value of int i is -1 Value of float i_float is -1.0 Value of int i is 0 Value of float i_float is 0.0 Value of int i is 1000 Value of float i_float is 1000.0

DONE int to string #

import std /[ strformat , strutils ] let i_seq : seq [ int ] = @[- 1000 , 0 , 1000 ] for i in i_seq : echo & "Value of { $type (i)} i is {i}" var i_str1 = $ i i_str2 = & "{i}" i_str3 = intToStr ( i ) # strutils echo & " Value of { $type (i_str1)} i_str1 is {i_str1}" echo & " Value of { $type (i_str2)} i_str2 is {i_str2}" echo & " Value of { $type (i_str3)} i_str3 is {i_str3}"

Value of int i is -1000 Value of string i_str1 is -1000 Value of string i_str2 is -1000 Value of string i_str3 is -1000 Value of int i is 0 Value of string i_str1 is 0 Value of string i_str2 is 0 Value of string i_str3 is 0 Value of int i is 1000 Value of string i_str1 is 1000 Value of string i_str2 is 1000 Value of string i_str3 is 1000

Why is even intToStr needed? #

As we see above $ provides a very concise way to print integers as strings. On the other hand, intToStr is verbose and an oddly named function.. the only one named as fooToBar (we don’t have strToInt , but parseInt .. I wish it were named as the former). Also, if one wants to use intToStr , they need to first import strutils .

From intToStr docs:

proc intToStr ( x : int ; minchars : Positive = 1 ): string {..} Converts x to its decimal representation. The resulting string will be minimally minchars characters long. This is achieved by adding leading zeros.

So intToStr would be used to add leading zeros:

import std /[ strutils ] let i_arr = [- 100 , - 50 , 0 , 0 , 123 , 1000 ] for i in i_arr : echo intToStr ( i , 10 )

-0000000100 -0000000050 0000000000 0000000000 0000000123 0000001000

The minchars parameter in intToStr does not count the negative sign character.

But then, a similar result also can be achieved by the general formatting macro & from strformat . See below to see what I mean by “similar”.

import std /[ strformat ] let i_arr = [- 100 , - 50 , 0 , 0 , 123 , 1000 ] for i in i_arr : echo & "{i: 011}"

-0000000100 -0000000050 0000000000 0000000000 0000000123 0000001000

Breakdown of the “ 011” format specifier —

Here is the general & / fmt format specifier syntax:

[[fill]align][sign][#][0][minimumwidth][.precision][type]

The initial space is the ‘[sign]’ part which indicates that space should be used for positive numbers. I used that so that the positive numbers align well (right align) with the negative numbers.

The “0” is the ‘[0]’ part, which 0-pads the numbers on the left.

“11” is the ‘[minimumwidth]’ part which counts the “-” sign character too. The minimum width is set to 11 to match the 10 argument given to the intToStr in the above example.

I prefer the fmt behavior better. But also, as floatToStr does not exist, and I would anyways need to resort to fmt for formatting negative floats, I might as well do the same for negative ints too — Consistency.

So, still.. there isn’t a strong case to use intToStr .

From float #

DONE float to bool #

Any float value between (-1.0, 1.0) is false .

value between is . All other float values are true .

import std /[ strformat ] let f_seq = @[- 1.0 , - 0.99 , - 0.1 , 0.0 , 0.3 , 0.5 , 0.8 , 1.0 , 1.1 , 2.0 ] for f in f_seq : var f_bool = f . bool echo & "Value of { $type (f)} f is {f}" echo & "Value of { $type (f_bool)} f_bool is {f_bool}"

Value of float f is -1.0 Value of bool f_bool is true Value of float f is -0.99 Value of bool f_bool is false Value of float f is -0.1 Value of bool f_bool is false Value of float f is 0.0 Value of bool f_bool is false Value of float f is 0.3 Value of bool f_bool is false Value of float f is 0.5 Value of bool f_bool is false Value of float f is 0.8 Value of bool f_bool is false Value of float f is 1.0 Value of bool f_bool is true Value of float f is 1.1 Value of bool f_bool is true Value of float f is 2.0 Value of bool f_bool is true

DONE float to char (truncation + rollover) #

Floats whose truncated integer values are >= 256 or < 0 will roll-over to a char value in the range '\0' .. '\255' as shown in the below snippet.

import std /[ strformat ] let f_seq : seq [ float ] = @[- 2.0 , - 1.5 , - 1.1 , 0.0 , 0.4 , 1.0 , 65.0 , 65.3 , 255.0 , 256.0 , 257.0 ] for f in f_seq : var f_char = f . char echo & "Value of { $type (f)} f is {f}" echo & "Value of { $type (f_char)} f_char is {repr(f_char)}"

Value of float f is -2.0 Value of char f_char is '\254' Value of float f is -1.5 Value of char f_char is '\255' Value of float f is -1.1 Value of char f_char is '\255' Value of float f is 0.0 Value of char f_char is '\0' Value of float f is 0.4 Value of char f_char is '\0' Value of float f is 1.0 Value of char f_char is '\1' Value of float f is 65.0 Value of char f_char is 'A' Value of float f is 65.3 Value of char f_char is 'A' Value of float f is 255.0 Value of char f_char is '\255' Value of float f is 256.0 Value of char f_char is '\0' Value of float f is 257.0 Value of char f_char is '\1'

DONE float to int (truncation + rollover) #

Note from the below example that rollover happens at extremes of the int values.

import std /[ strformat , math ] let f_seq : seq [ float ] = @[ low ( int ). float , ( low ( int ) / 2 ). float , - 1.9 , - 1.5 , - 1.1 , 0.0 , 0.4 , 0.5 , 0.9 , 1.0 , ( high ( int ) / 2 ). float , high ( int ). float ] for f in f_seq : var f_int = f . int echo & "Value of { $type (f)} f is {f}" echo & "Value of { $type (f_int)} f_int is {f_int}"

Value of float f is -9.223372036854776e+18 Value of int f_int is -9223372036854775808 Value of float f is -4.611686018427388e+18 Value of int f_int is -4611686018427387904 Value of float f is -1.9 Value of int f_int is -1 Value of float f is -1.5 Value of int f_int is -1 Value of float f is -1.1 Value of int f_int is -1 Value of float f is 0.0 Value of int f_int is 0 Value of float f is 0.4 Value of int f_int is 0 Value of float f is 0.5 Value of int f_int is 0 Value of float f is 0.9 Value of int f_int is 0 Value of float f is 1.0 Value of int f_int is 1 Value of float f is 4.611686018427388e+18 Value of int f_int is 4611686018427387904 Value of float f is 9.223372036854776e+18 Value of int f_int is -9223372036854775808

DONE float to string #

import std /[ strformat ] let f_seq : seq [ float ] = @[- 1.9 , - 1.5 , - 1.1 , 0.0 , 0.4 , 0.5 , 0.9 , 1.0 ] for f in f_seq : var f_string1 = $ f f_string2 = & "{f}" echo & "Value of { $type (f)} f is {f}" echo & " Value of { $type (f_string1)} f_string1 is {f_string1}" echo & " Value of { $type (f_string2)} f_string2 is {f_string2}"

Value of float f is -1.9 Value of string f_string1 is -1.9 Value of string f_string2 is -1.9 Value of float f is -1.5 Value of string f_string1 is -1.5 Value of string f_string2 is -1.5 Value of float f is -1.1 Value of string f_string1 is -1.1 Value of string f_string2 is -1.1 Value of float f is 0.0 Value of string f_string1 is 0.0 Value of string f_string2 is 0.0 Value of float f is 0.4 Value of string f_string1 is 0.4 Value of string f_string2 is 0.4 Value of float f is 0.5 Value of string f_string1 is 0.5 Value of string f_string2 is 0.5 Value of float f is 0.9 Value of string f_string1 is 0.9 Value of string f_string2 is 0.9 Value of float f is 1.0 Value of string f_string1 is 1.0 Value of string f_string2 is 1.0

From string #

DONE string to bool #

import std /[ strformat , strutils ] let s_seq = @[ "true" , "True" , "tRuE" , "false" , "False" , "FaLsE" ] for s in s_seq : var s_bool = parseBool ( s ) echo & "Value of { $type (s)} s is {s}" echo & "Value of { $type (s_bool)} s_bool is {s_bool}"

Value of string s is true Value of bool s_bool is true Value of string s is True Value of bool s_bool is true Value of string s is tRuE Value of bool s_bool is true Value of string s is false Value of bool s_bool is false Value of string s is False Value of bool s_bool is false Value of string s is FaLsE Value of bool s_bool is false

DONE string to char #

A string can be represented as a sequence of chars as shown below.

import std /[ strformat ] let s = "abcd

" var c_seq : seq [ char ] for c in s : c_seq . add ( c ) echo & "Value of { $type (s)} s is {s}" echo & "Value of { $type (c_seq)} c_seq is {c_seq}" for i , c in c_seq : echo & " c_seq[{i}] = {repr(c)} (type: { $type (c)})"

Value of string s is abcd Value of seq[char] c_seq is @['a', 'b', 'c', 'd', '

'] c_seq[0] = 'a' (type: char) c_seq[1] = 'b' (type: char) c_seq[2] = 'c' (type: char) c_seq[3] = 'd' (type: char) c_seq[4] = '\10' (type: char)

DONE string to int #

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

Value of string s is 1212 Value of int s_int is 1212

DONE string to float #

import std /[ strformat , strutils ] let s = "12.12" s_float = parseFloat ( s ) echo & "Value of { $type (s)} s is {s}" echo & "Value of { $type (s_float)} s_float is {s_float}"

Value of string s is 12.12 Value of float s_float is 12.12

Data Types #

DONE int types #

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

Below code is compiled in 32-bit mode:

import std /[ strformat ] echo & "Size of int32 / int / int64 = {sizeof(int32)} / *{sizeof(int)}* / {sizeof(int64)}"

Size of int32 / int / int64 = 4 / *4* / 8

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

import std /[ strformat ] echo & "Size of int32 / int / int64 = {sizeof(int32)} / *{sizeof(int)}* / {sizeof(int64)}"

Size of int32 / int / int64 = 4 / *8* / 8

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

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

aInt64 = 5, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 1 aInt64 = 1, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 1 aInt64 = 1, aInt32 = 4, aInt16 = 3, aInt8 = 2, aInt = 4 aInt64 = 1, aInt32 = 3, aInt16 = 3, aInt8 = 2, aInt = 4 aInt64 = 1, aInt32 = 3, aInt16 = 2, aInt8 = 2, aInt = 4

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

Here’s my attempt at explaining that:

int64 > int > int32 > int16 > int8



My home-grown theory follows:

int64 is at the “highest level”, and int8 is at the “lowest level”.

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

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

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

Related:

Char and repr #

When printing char values for debug, it’s better to print using repr so that unprintable chars like ACK ( \6 ) and BELL ( \7 ) can be easily distinguished. See below for example:

for c in @[ ' \6 ' , ' \7 ' , ' \32 ' , ' \33 ' , ' \65 ' , 'B' ] : echo "char with ascii value of " , c . ord , " = " , repr ( c )

char with ascii value of 6 = '\6' char with ascii value of 7 = '\7' char with ascii value of 32 = ' ' char with ascii value of 33 = '!' char with ascii value of 65 = 'A' char with ascii value of 66 = 'B'

Getting min and max for various data types #

Using low and high #

import std /[ math , strformat ] let int_low = low ( int ) int_low_2power = log2 ( - int_low . float ). int int32_low = low ( int32 ) int32_low_2power = log2 ( - int32_low . float ). int int64_low = low ( int64 ) int64_low_2power = log2 ( - int64_low . float ). int echo & "int: {low(int)} (-2^{int_low_2power}) -> {high(int)} (2^{int_low_2power} - 1)" echo & "int32: {low(int32)} (-2^{int32_low_2power}) -> {high(int32)} (2^{int32_low_2power} - 1)" echo & "int64: {low(int64)} (-2^{int64_low_2power}) -> {high(int64)} (2^{int64_low_2power} - 1)" echo & "float: {low(float)} -> {high(float)}" echo & "float32: {low(float32)} -> {high(float32)}" echo & "float64: {low(float64)} -> {high(float64)}"

int: -9223372036854775808 (-2^63) -> 9223372036854775807 (2^63 - 1) int32: -2147483648 (-2^31) -> 2147483647 (2^31 - 1) int64: -9223372036854775808 (-2^63) -> 9223372036854775807 (2^63 - 1) float: -inf -> inf float32: -inf -> inf float64: -inf -> inf

Using sizeof #

From sizeof docs:

returns the size of x in bytes. Since this is a low-level proc, its usage is discouraged - using new for the most cases suffices that one never needs to know x’s size. As a special semantic rule, x may also be a type identifier ( sizeof(int) is valid).

import std /[ strformat ] echo & "Size of bool is {sizeof(bool)} byte" echo & "Size of char is {sizeof(char)} byte" echo & "Size of int is {sizeof(int)} bytes" echo & "Size of int32 is {sizeof(int32)} bytes" echo & "Size of int64 is {sizeof(int64)} bytes" echo & "Size of float is {sizeof(float)} bytes" echo & "Size of float32 is {sizeof(float32)} bytes" echo & "Size of float64 is {sizeof(float64)} bytes"

Size of bool is 1 byte Size of char is 1 byte Size of int is 8 bytes Size of int32 is 4 bytes Size of int64 is 8 bytes Size of float is 8 bytes Size of float32 is 4 bytes Size of float64 is 8 bytes

Boolean checking “unset” variables #

isNil non-string primitive types #

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

import std /[ strformat ] var b : bool c : char i : int f : float echo & "initial value of { $type (b)} b = {b}, isNil? {b.isNil}" echo & "initial value of { $type (c)} c = {c}, isNil? {c.isNil}" echo & "initial value of { $type (i)} i = {i}, isNil? {i.isNil}" echo & "initial value of { $type (f)} f = {f}, isNil? {f.isNil}"

Trying to evaluate the above gives this error (and then the similar for those other types in that code snippet too):

lib/pure/strformat.nim(315, 39) Error: type mismatch: got <bool> but expected one of: proc isNil[T](x: seq[T]): bool proc isNil(x: cstring): bool proc isNil(x: string): bool proc isNil[T](x: ptr T): bool proc isNil[T](x: ref T): bool proc isNil(x: pointer): bool proc isNil[T: proc](x: T): bool expression: isNil(b) /bin/sh: /tmp/babel-2oW0wY/nim_src_FoNExx: Permission denied

isNil on string #

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

var s : string echo s . isNil ()

Trying to do so will give this error:

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

isNil on File object #

Thanks to mashingan from Nim Forum for this tip.

var fo : File echo "Before open: Is fo 'unset'? " , fo . isNil fo = open ( "./nim.org" ) echo "After open: Is fo 'unset'? " , fo . isNil fo . close echo "After close: Is fo 'unset'? " , fo . isNil fo = nil echo "After explicitly setting to nil: Is fo 'unset'? " , fo . isNil

Before open: Is fo 'unset'? true After open: Is fo 'unset'? false After close: Is fo 'unset'? false After explicitly setting to nil: Is fo 'unset'? true

String Stuff #

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

String Functions #

% and format #

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

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

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

a b a b 100 200

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

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

a b a b a b 100 200

strformat and & / fmt #

This is a standard Nim library.

Refer to this separate set of notes that explains fmt (or & ) from the strformat library in great detail, including examples for all options in the fmt format specifier.

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

import std /[ strformat ] let a = 100 b = "abc" echo & "a = {a}, b = {b}"

a = 100, b = abc

TO BE FIXED Use string variable containing message formatting for fmt #

Below does not work:

import std /[ strformat ] let a = 100 msg = "a = {a}" echo fmt ( msg )

Gives error:

stack trace: (most recent call last) lib/pure/strformat.nim(281) & nim_src_ylMbyJ.nim(9, 10) Error: string formatting (fmt(), &) only works with string literals

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

String functions: Nim vs Python #

Refer to this separate set of notes where I compare the Nim String functions with those in Python 3.

String and character literals #

String literals are enclosed in double quotes

Character literals in single quotes. The single quote character literal is represented by '\39' .



Special characters are escaped with \ :

means newline, \t means tabulator, etc.

echo "Hello" # echo 'Hello' # This will give error; single quotes are only for single character echo 'a' , 'b' , ' \39 ' echo "c

" , ' \t ' , 'b'

Hello ab' c b

There are also raw string literals:

echo r"No need to escape

and \t in here"

No need to escape

and \t in here

In raw literals the backslash is not an escape character.

The third and last way to write string literals are long string literals like in Python. They are written with three quotes: """ ... """ ; they can span over multiple lines and the \ is not an escape character either.

echo """Hey look at this. I can keep on typing over multiple lines, with 'single' or "double" quotes and even back slashes:

\t \r"""

Hey look at this. I can keep on typing over multiple lines, with 'single' or "double" quotes and even back slashes:

\t \r

String concatenation #

Use & .

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

abcdef ghijkl

String Comparison #

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

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

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

If strings X and Y are of equal lengths, walking through the characters of both strings from the left-most side, for the first set of non-equal characters char-in-X and char-in-Y, char-in-X < char-in-Y => X < Y. assert "a" < "b" assert "bb" > "ba" assert "ab" < "ba" assert "abc" < "abd" assert "bbc" > "abc"

The above rule applies even if strings X and Y are not of equal lengths. So even if string X has more characters than string Y, X would be less than Y if for the first set of non-equal characters char-in-X and char-in-Y, char-in-X < char-in-Y. assert "a" < "ab" assert "ab" > "a" assert "ab" < "b"

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

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

See Tuple comparison instead for Nim version comparison.

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

The functions from strutils like isAlphaNumeric , isAlphaAscii work on chars and strings.

like , work on chars and strings. The functions from unicode like isAlpha , isLower , isUpper , etc. work on Runes and strings.

import std /[ unicode ] from std / strutils import isAlphaNumeric , isAlphaAscii echo 'a' echo 'a' . isAlphaAscii () echo 'a' . isAlphaNumeric () # echo 'a'.isAlpha() # this gives error: nim_src_YQy3FE.nim(6, 9) Error: type mismatch: got <char> echo 'a' . Rune echo 'a' . Rune . isLower () echo 'a' . Rune . isUpper () echo 'a' . Rune . isAlpha ()

a true true a true false true

Walking through chars vs Runes #

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

import std /[ strformat ] let str = "કૌશલ" echo "Here is how it looks when str is parsed char-by-char:" echo & " str = {str}, number of chars = {str.len}" for i , c in str : echo & " char {i} = {repr(c)} ({ord(c):#x})"

Code Snippet 2 chars in a string : Walking throughin a string

Here is how it looks when str is parsed char-by-char: str = કૌશલ, number of chars = 12 char 0 = '\224' (0xe0) char 1 = '\170' (0xaa) char 2 = '\149' (0x95) char 3 = '\224' (0xe0) char 4 = '\171' (0xab) char 5 = '\140' (0x8c) char 6 = '\224' (0xe0) char 7 = '\170' (0xaa) char 8 = '\182' (0xb6) char 9 = '\224' (0xe0) char 10 = '\170' (0xaa) char 11 = '\178' (0xb2)

import std /[ unicode , strformat ] let str = "કૌશલ" echo "Here is how it looks when str is parsed rune-by-rune:" echo & " str = {str}, number of Runes = {str.runeLen}" for i , r in str . toRunes : echo & " Rune {i} = {r}"

Code Snippet 3 Runes in a string : Walking throughin a string

Here is how it looks when str is parsed rune-by-rune: str = કૌશલ, number of Runes = 4 Rune 0 = ક Rune 1 = ૌ Rune 2 = શ Rune 3 = લ

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

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

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

import std /[ unidecode / unidecode ] # or just import unidecode echo unidecode ( "મારુ નામ કૌશલ મોદી છે. હૂં અમદાવાદ થી છું." ) echo unidecode ( "Æneid" ) echo unidecode ( "北京" ) # Below is the same as above echo unidecode ( " \xe5\x8c\x97\xe4\xba\xac " )

Code Snippet 4 unidecode (compiled with -d:embedUnidecodeTable ) : Example of transliteration using(compiled

maaru naam kaushl modii che. huuN amdaavaad thii chuN. AEneid Bei Jing Bei Jing

In Emacs, with point over 北, if I do C-u C-x = , I get: buffer code: #xE5 #x8C #x97 So I translated that to \xe5\x8c\x97 , and then the same for 京.

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

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

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

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

import std /[ unidecode / unidecode ] # or just import unidecode import std /[ os ] proc findNimStdLib * (): string = ## Tries to find a path to a valid "system.nim" file. ## Returns "" on failure. try : let nimexe = os . findExe ( "nim" ) if nimexe . len == 0 : return "" result = nimexe . splitPath () [ 0 ] / .. / "lib" if not fileExists ( result / "system.nim" ): when defined ( unix ): result = nimexe . expandSymlink . splitPath () [ 0 ] / .. / "lib" if not fileExists ( result / "system.nim" ): return "" except OSError , ValueError : return "" # Load the Unicode data file. loadUnidecodeTable ( findNimStdLib () / "pure/unidecode/unidecode.dat" ) echo unidecode ( "મારુ નામ કૌશલ મોદી છે. હૂં અમદાવાદ થી છું." ) echo unidecode ( "Æneid" ) echo unidecode ( "北京" ) # Below is the same as above echo unidecode ( " \xe5\x8c\x97\xe4\xba\xac " )

Code Snippet 5 unidecode (compiled without -d:embedUnidecodeTable ) : Example of transliteration using(compiled without

maaru naam kaushl modii che. huuN amdaavaad thii chuN. AEneid Bei Jing Bei Jing

See Nim StdLib path for the source of findNimStdLib .

Call randomize before rand #

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

import std /[ random ] echo rand ( high ( int ))

Code Snippet 6 rand without randomize without

4292486321577947087

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

import std /[ random ] randomize () echo rand ( high ( int ))

Code Snippet 7 rand with randomize , no seed with, no seed

8191563575897872325

Specifying randomization seed #

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

Specifying randomization seed using randomize #

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

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

import std /[ random ] randomize ( 123 ) # arbitrary integer seed echo rand ( high ( int ))

Code Snippet 8 randomize to specify a randomization seed : Usingto specify a randomization seed

8452497653883

FIXED randomize(0) disables randomization #

From tests, it looks like randomize(0) disables randomization altogether and hard-codes the “randomized” var to 0!

Whether this is intended or not, this behavior is quite odd.

This was indeed odd, and is now fixed in 304b1dd34bc52.

import std /[ random ] echo "Setting randomization seed to a non-zero value; value of 1 is picked arbitrarily:" randomize ( 1 ) for _ in 0 .. 4 : echo rand ( high ( int )) echo "

Now setting randomization seed to 0:" randomize ( 0 ) for _ in 0 .. 4 : echo rand ( high ( int ))

After the fix, above code gives this error:

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

But earlier, it gave this strange output:

Setting randomization seed to a non-zero value; value of 1 is picked arbitrarily: 68719493121 38280734540038433 1153018330890649890 1842080154353508865 97957842178638929 Now setting randomization seed to 0: 0 0 0 0 0

Getting the same Rand state using initRand #

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

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

Code Snippet 9 initRand to get the same Rand state each time : Usingto get the samestate each time

6234675270030254125

Random bool #

import std /[ random ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests for _ in 0 .. 5 : echo rs . rand ( 1 ). bool

true false false false true true

Random range #

Use rand(LOWER .. UPPER) .

import std /[ random ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests for _ in 0 .. 5 : echo rs . rand ( 4 .. 5 )

5 4 4 4 5 5

Works with negatives too:

import std /[ random ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests for _ in 0 .. 5 : echo rs . rand ( - 5 .. 5 )

-4 -3 -5 -4 1 -4

rand range #

import std /[ random ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests let k = 3 for _ in 0 .. ( 2 * k ): echo rs . rand ( k )

1 2 0 2 3 3 3

As seen above, rand(k) returns a random value in the range [0,k] i.e. including the “k” value.

Limit of rand #

rand accepts int (I think that is same is int64 – because this) and float (probably same as float32 ? or float64 ?.. couldn’t tell from that).

From random.rand docs:

Returns a random number in the range 0 .. max.

As rand accepts int , and it’s size is same as int64 , and is signed (use uint for unsigned), the max positive number that rand can generate is 2^63 - 1 .

DONE rand does not accept int64 input #

Below gives an error.

import std /[ random , strformat ] echo & "rand(9223372036854775807) = {rand(9223372036854775807)}"

nim_src_yz0syY.nim(5, 10) Error: type mismatch: got <int64> but expected one of: proc rand[T](r: var Rand; x: HSlice[T, T]): T proc rand(max: float): float proc rand(max: int): int proc rand(r: var Rand; max: float): float proc rand[T](r: var Rand; a: openArray[T]): T proc rand[T](a: openArray[T]): T proc rand[T](x: HSlice[T, T]): T proc rand(r: var Rand; max: int): int expression: rand(9223372036854775807'i64)

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

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

import std /[ random , strformat ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests echo & "rand(9223372036854775807.int) = {rs.rand(9223372036854775807.int)}"

rand(9223372036854775807.int) = 6234675270030254125

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

import std /[ math ] proc log [ X , B : SomeFloat ] ( x : X , base : B = E ): auto = ## Computes the logarithm ``base`` of ``x``. ## If ``base`` is not specified, it defaults to the natural base ``E``. when B is float64 or X is float64 : var r : float64 else : var r : float32 if base == E : r = ln ( x ) else : r = ln ( x ) / ln ( base ) return r import std /[ random , strformat ] echo & "log10(111) = {log(111.float, 10.float)}" echo & "log2(8) = {log(8.float, 2.float)}" echo & "ln(8) = {log(8.float, E)}" echo & "ln(8) = {log(8.float)}"

log10(111) = 2.045322978786657 log2(8) = 3.0 ln(8) = 2.079441541679836 ln(8) = 2.079441541679836

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

2^3.0 = 8

import std /[ math , strformat ] var val = 100 power10 = log10 ( val . float ) # need to cast val to a float echo & "10^{power10} = {val}"

10^2.0 = 100

Using ^ from math module (Non-negative integer exponentiation) #

Need to import math module for the exponentiation operator ^ to work.

You can do X ^ Y where X can be an integer or float, but Y has to be an integer >= 0.

import std /[ math ] echo ( 2 ^ 0 ) echo ( 2 ^ 3 ) echo ( 2.2 ^ 3 )

1 8 10.648

Using pow from math module (Float exponentiation) #

As mentioned above, X ^ Y works only if Y is an integer and >= 0.

But what if Y is negative, or not an integer? .. In that case, you need to use the pow function from the same math module.

import std /[ math ] echo "Evaluating equivalent of 2^-1:" echo pow ( 2.0 , - 1.0 )

Evaluating equivalent of 2^-1: 0.5

About pow and floats #

Note that using pow has a requirement – Both of its arguments need to be floats. So the below snippet will fail:

import std /[ math ] echo pow ( 2 , 1 )

nim_src_We7gQw.nim(5, 9) Error: ambiguous call; both math.pow(x: float64, y: float64)[declared in lib/pure/math.nim(265, 7)] and math.pow(x: float32, y: float32)[declared in lib/pure/math.nim(264, 7)] match for: (int literal(2), int literal(1))

But below will work:

import std /[ math ] echo pow ( 2.0 , 1.0 ) echo pow ( 2. float , 1. float ) # same as above

2.0 2.0

Quotient and Remainder #

If you do “7 / 2”, the quotient is 3, and the remainder is 1.

The quotient is calculated using the binary operator div which basically does integer division.

The result is negative if either of the dividend or the divisor is negative; else it is positive.

import std /[ strformat ] echo "divisor = 3" for i in - 4 .. 4 : echo & " {i:2} div 3 = {i div 3:2}" echo "

divisor = -3" for i in - 4 .. 4 : echo & " {i:2} div -3 = {i div -3:2}"

divisor = 3 -4 div 3 = -1 -3 div 3 = -1 -2 div 3 = 0 -1 div 3 = 0 0 div 3 = 0 1 div 3 = 0 2 div 3 = 0 3 div 3 = 1 4 div 3 = 1 divisor = -3 -4 div -3 = 1 -3 div -3 = 1 -2 div -3 = 0 -1 div -3 = 0 0 div -3 = 0 1 div -3 = 0 2 div -3 = 0 3 div -3 = -1 4 div -3 = -1

Remainder / Modulo operator #

The remainder is calculated using the binary operator, Modulo operator, mod .

The result has the sign of the dividend i.e. the sign of the divisor is ignored.

import std /[ strformat ] echo "divisor = 3" for i in - 4 .. 4 : echo & " {i:2} mod 3 = {i mod 3:2}" echo "

divisor = -3" for i in - 4 .. 4 : echo & " {i:2} mod -3 = {i mod -3:2}"

divisor = 3 -4 mod 3 = -1 -3 mod 3 = 0 -2 mod 3 = -2 -1 mod 3 = -1 0 mod 3 = 0 1 mod 3 = 1 2 mod 3 = 2 3 mod 3 = 0 4 mod 3 = 1 divisor = -3 -4 mod -3 = -1 -3 mod -3 = 0 -2 mod -3 = -2 -1 mod -3 = -1 0 mod -3 = 0 1 mod -3 = 1 2 mod -3 = 2 3 mod -3 = 0 4 mod -3 = 1

inc proc increments variables of Ordinal types: int , char , enum .

inc a is similar to doing a = a + 1 or a += 1 for int or char type variable a .

var a = 100 echo a inc a echo a inc a echo a

100 101 102

var a = 'b' echo a inc a echo a inc a echo a

b c d

type Direction = enum north , east , south , west var a = north echo a inc a echo a inc a echo a

north east south

dec proc decrements variables of Ordinal types: int , char , enum .

dec a is similar to doing a = a - 1 or a -= 1 for int or char type variable a .

var a = 100 echo a dec a echo a dec a echo a

100 99 98

var a = 'b' echo a dec a echo a dec a echo a

b a `

type Direction = enum north , east , south , west var a = west echo a dec a echo a dec a echo a

west south east

Bitwise operations #

Bitwise And #

import std /[ strformat ] let a = 15 b = 1 var c = ( a + b ) and 0x0f echo & "4-bit addition (overflow): {a:#03x} + {b:#03x} = {c:#03x}" echo & "4-bit addition (overflow): {a:#06b} + {b:#06b} = {c:#06b}"

Code Snippet 10 : Limiting output to 4-bits

4-bit addition (overflow): 0xf + 0x1 = 0x0 4-bit addition (overflow): 0b1111 + 0b0001 = 0b0000

Table 2 var vs let vs const vsvs Keyword Variable type Must be initialized? Can be set during runtime? Should be evaluable at compile? var Mutable No (type must be specified though if not initialized) Yes, multiple times No let Immutable Yes, though these can be initialized at run time. Yes, just once No const Constant Yes No Yes

Mutable variables ( var ) #

var variables are mutable.

var a = "foo" b = 0 c : int # Works fine, initialized to 0 # Works fine, `a` is mutable a . add ( "bar" ) echo a b += 1 echo b c = 3 echo c c = 7 echo c

foobar 1 3 7

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

ref

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

Not 1 Is 1

Immutable variables ( let ) #

let variables are not mutable, but they can be set at run time.

let d = "foo" e = 5 # Compile-time error, must be initialized at creation # f: float # Below line fixes the error f : float = 2.2 # Compile-time error, `d` and `e` are immutable # Below 2 lines are commented out to fix the compilation error # d.add("bar") # e += 1 echo d echo e echo f

foo 5 2.2

Constants ( const ) #

const “variables” are not mutable, and they have to be set at compile time.

# Computed at compilation time const s = "abcdef" sLen = s . len echo s echo sLen

abcdef 6

let vs const #

The difference between let and const is:

let allows a variable value to be assigned at run time (though it cannot be re-assigned). let input = readLine ( stdin ) # works

const means “enforce compile time evaluation and put it into a data section” i.e. you should be able to evaluate the value of a const variable during compile time. So setting a const variable using readLine (that takes user input at run time) will result in an error. const input = readLine ( stdin ) # Error: constant expression expected

return keyword and result Variable #

Below example shows the use of return keyword:

proc getAlphabet (): string = var accm = "" for letter in 'a' .. 'z' : # see iterators accm . add ( letter ) return accm echo getAlphabet ()

Code Snippet 11 getAlphabet function implementation without using result variable function implementationusingvariable

abcdefghijklmnopqrstuvwxyz

The result variable is a special variable that serves as an implicit return variable, which exists because the control flow semantics of the return statement are rarely needed. The result variable is initialized in the standard way, as if it was declared with:

var result : ReturnType

For example, the getAlphabet() function above could be rewritten more concisely as:

proc getAlphabet (): string = result = "" for letter in 'a' .. 'z' : result . add ( letter ) echo getAlphabet ()

Code Snippet 12 getAlphabet function implementation using result variable function implementation usingvariable

abcdefghijklmnopqrstuvwxyz

A possible gotcha is declaring a new variable called result and expecting it to have the same semantics.

proc unexpected (): int = var result = 5 # Uh-oh, here 'result' got declared as a local variable because of 'var' keyword result += 5 echo unexpected () # Prints 0, not 10

0

Global variables (like the ones in below example) cannot remain uninitialized in Nim.. they are always auto-initialized to some value.

var fooBool : bool fooInt : int fooFloat : float fooString : string fooSeqString : seq [ string ] echo "Value of 'uninitialized' variable 'fooBool': " , fooBool echo "Value of 'uninitialized' variable 'fooInt': " , fooInt echo "Value of 'uninitialized' variable 'fooFloat': " , fooFloat echo "Value of 'uninitialized' variable 'fooString': " , fooString echo "Value of 'uninitialized' variable 'fooSeqString': " , fooSeqString

Code Snippet 13 : Global variables always get auto-initialized

Value of 'uninitialized' variable 'fooBool': false Value of 'uninitialized' variable 'fooInt': 0 Value of 'uninitialized' variable 'fooFloat': 0.0 Value of 'uninitialized' variable 'fooString': Value of 'uninitialized' variable 'fooSeqString': @[]

Uninitialized variables #

From section Variable Auto-initialization, we see that global variables always get auto-initialized.

Thanks to @data-man from GitHub, I learn that local variables can remain uninitialized, with the use of the noinit pragma.

Local variables would be the ones like the var variables inside a proc .

As global variables always auto-initialize, the {.noinit.} pragma will not work in the below snippet. The ui_i var still auto-initializes with all elements set to 0.

Nim should have thrown an error if someone tried to use {.noinit.} for global vars, but instead it silently does nothing!

var ui_i {. noinit .}: array [ 3 , int ] echo "Value of uninitialized variable 'ui_i': " , ui_i

Code Snippet 14 noinit pragma does not work for global variables pragma does not work for global variables

Value of uninitialized variable 'ui_i': [0, 0, 0]

Pollute Stack #

A little function PolluteStack to help test if {.noinit.} works.

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

I came up with the above function after reading this priceless tip by Stefan Salevski:

You may try to call another proc which writes some values to local variables, that should pollute the stack. And then call your ff proc.

I am open to learn better ways to pollute the stack.

Break-down of the polluteStack function #

We will use just this one line for analysis, as the rest are similar.

f32 : float32 = ( rs . rand ( high ( int64 ). int )). float32

f32 is of type float32 . So whatever we are assigning it to must be of that type. Here, that whatever is (rs.rand(high(int64).int)) , and we are casting it to float32 type using (WHATEVER).float32 .

is of type . So whatever we are assigning it to must be of that type. Here, that whatever is , and we are casting it to type using . So here whatever is rand which takes in high(int64).int as input.

which takes in as input. The input to rand is casted to int using .int because as of writing this, it did not accept inputs of type int64 . high(int64) returns the maximum value of int64 type.

is casted to using because as of writing this, it did not accept inputs of type . Both float32 and float64 have min value of -inf and max value of +inf . The 32-bit/64-bit only changes the number of bytes using internally for storing those float values. So it is OK to use the same rand(high(int64).int to generate a random number for both of these float types as long as we are casting them using the right type for the right variable.

For simplicity, this function generates only positive random values for each type.

Test polluteStack #

import std /[ random , math ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests proc polluteStack () = var c : char = ( rs . rand ( high ( char ). int )). char i32 : int32 = ( rs . rand ( high ( int32 ). int )). int32 i64 : int64 = ( rs . rand ( high ( int64 ). int )). int64 f32 : float32 = ( rs . rand ( high ( int64 ). int )). float32 # Intentionally using high(int64); not a typo f64 : float64 = ( rs . rand ( high ( int64 ). int )). float64 b : bool = rs . rand ( 1 ). bool echo repr ( c ) echo i32 echo i64 echo f32 echo f64 echo b polluteStack ()

'-' 142076510 5077287487108608156 6.414006028433621e+18 5.441250298499811e+18 true

{.noinit.} does not work in block too #

I thought that variables in a block would be local for the purpose of {.noinit.} . But they are not i.e. the no-initialization does not work here too!

import std /[ random , math ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests proc polluteStack () = var c : char = ( rs . rand ( high ( char ). int )). char i32 : int32 = ( rs . rand ( high ( int32 ). int )). int32 i64 : int64 = ( rs . rand ( high ( int64 ). int )). int64 f32 : float32 = ( rs . rand ( high ( int64 ). int )). float32 # Intentionally using high(int64); not a typo f64 : float64 = ( rs . rand ( high ( int64 ). int )). float64 b : bool = rs . rand ( 1 ). bool # local var in block block foo : polluteStack () var ui_i {. noinit .}: array [ 3 , int ] echo "In block: Value of uninitialized variable 'ui_i' in block: " , ui_i

In block: Value of uninitialized variable 'ui_i' in block: [0, 0, 0]

Works for var variables in proc #

Finally, the below code returns random values as that local var , local to a proc will actually remain uninitialized:

import std /[ random , math ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests proc polluteStack () = var c : char = ( rs . rand ( high ( char ). int )). char i32 : int32 = ( rs . rand ( high ( int32 ). int )). int32 i64 : int64 = ( rs . rand ( high ( int64 ). int )). int64 f32 : float32 = ( rs . rand ( high ( int64 ). int )). float32 # Intentionally using high(int64); not a typo f64 : float64 = ( rs . rand ( high ( int64 ). int )). float64 b : bool = rs . rand ( 1 ). bool proc a () = var ui_i1 {. noinit .}: int ui_i2 {. noinit .}: array [ 3 , int ] echo "Value of uninitialized variable 'ui_i1': " , ui_i1 echo "Value of uninitialized variable 'ui_i2': " , ui_i2 polluteStack () a ()

Code Snippet 15 noinit pragma works for local variables pragma works for local variables

Value of uninitialized variable 'ui_i1': 45 Value of uninitialized variable 'ui_i2': [610213964383576109, 142076510, 3242591731706757120]

Earlier confusion Even for local vars, if the vars were not arrays, I was unable to have them echo with random values with the noinit pragma. Nim Issue #7852 has some interesting code snippets that behaved differently wrt noinit between me and data-man. Fix Above issue got fixed once I started using the polluteStack proc.

Examples of uninitialized arrays #

Here are some more examples of uninitialized local variables of array types:

import std /[ random , math ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests proc polluteStack () = var c : char = ( rs . rand ( high ( char ). int )). char i32 : int32 = ( rs . rand ( high ( int32 ). int )). int32 i64 : int64 = ( rs . rand ( high ( int64 ). int )). int64 f32 : float32 = ( rs . rand ( high ( int64 ). int )). float32 # Intentionally using high(int64); not a typo f64 : float64 = ( rs . rand ( high ( int64 ). int )). float64 b : bool = rs . rand ( 1 ). bool proc a () = var ai {. noinit .}: array [ 3 , int ] ac {. noinit .}: array [ 3 , char ] ab {. noinit .}: array [ 3 , bool ] af {. noinit .}: array [ 3 , float ] echo ai echo ac echo ab echo af polluteStack () a ()

[610213964383576109, 142076510, 3242591731706757120] ['&', 'v', 'F'] [true, true, true] [1.440454044262747e+148, 6.173961142613567e+120, 2.807682128342881e+31]

Earlier confusion If I commented out the int array declaration in the above code (of course, its echo too), the char and bool arrays would start auto-initializing, but not the float . Fix Above issue got fixed once I started using the polluteStack proc. The confusion was created because the stack was clean.. needed something to pollute the stack first.

Scalar local vars and noinit #

Earlier confusion Looks like noinit works only for the float64 var. Fix Above issue got fixed once I started using the polluteStack proc.

ref

import std /[ random , math ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests proc polluteStack () = var c : char = ( rs . rand ( high ( char ). int )). char i32 : int32 = ( rs . rand ( high ( int32 ). int )). int32 i64 : int64 = ( rs . rand ( high ( int64 ). int )). int64 f32 : float32 = ( rs . rand ( high ( int64 ). int )). float32 # Intentionally using high(int64); not a typo f64 : float64 = ( rs . rand ( high ( int64 ). int )). float64 b : bool = rs . rand ( 1 ). bool proc bar () = var i32 : int32 i64 : int64 f32 : float32 f64 : float64 i32ni {. noInit .}: int32 i64ni {. noInit .}: int64 f32ni {. noInit .}: float32 f64ni {. noInit .}: float64 echo "i32 (auto-init) = " , i32 echo "i64 (auto-init) = " , i64 echo "f32 (auto-init) = " , f32 echo "f64 (auto-init) = " , f64 echo "i32ni (no init) = " , i32ni echo "i64ni (no init) = " , i64ni echo "f32ni (no init) = " , f32ni echo "f64ni (no init) = " , f64ni polluteStack () bar ()

i32 (auto-init) = 0 i64 (auto-init) = 0 f32 (auto-init) = 0.0 f64 (auto-init) = 0.0 i32ni (no init) = 1182148113 i64ni (no init) = 5077287487108608156 f32ni (no init) = 2306817271005184.0 f64ni (no init) = 1.440454044262747e+148

Check Type ( is ) #

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

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

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

Heterogeneous Slice (HSlice) #

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

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

An HSlice can be created using below:

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

s1=`4 .. f' is of type HSlice[system.int, system.char]

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

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

s1=`4 .. f' is of type HSlice[system.int, system.char]

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

import std /[ strformat ] let hs1 = 1 .. 3 echo & "hs1=`{hs1}' is of type { $type (hs1)}" for n in hs1 : echo n let hs2 = 'k' .. 'm' echo & "hs2=`{hs2}' is of type { $type (hs2)}" for c in hs2 : echo c import std /[ random ] var rs = initRand ( 0xDEAD_BEEF ) # some fixed seed, ref Arraymancer tests echo "random num: " , rs . rand ( 8 .. 9 ) let str = "abcdefghijk" echo str [ 2 .. 4 ]

Code Snippet 16 HSlice : Slice iterators, random range, string slice : Slice iterators, random range, string slice

hs1=`1 .. 3' is of type HSlice[system.int, system.int] 1 2 3 hs2=`k .. m' is of type HSlice[system.char, system.char] k l m random num: 9 cde

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

import std /[ strformat ] let hs3 = 1 .. 'z' hs4 = 0.99 .. "blah" echo & "hs3=`{hs3}' is of type { $type (hs3)}" echo & "hs4=`{hs4}' is of type { $type (hs4)}"

Code Snippet 17 HSlice : Heterogeneous types : Heterogeneous types

hs3=`1 .. z' is of type HSlice[system.int, system.char] hs4=`0.99 .. blah' is of type HSlice[system.float64, system.string]

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

let str = "abcdefghijk" type MyEnum = enum Sixth = 5 Seventh echo str [ 3 .. Sixth ]

Code Snippet 18 HSlice : String slice with heterogeous type elements : String slice with heterogeous type elements

def

Here are few examples of nested HSlices:

import std /[ strformat ] let hs5 = 777 .. 'd' .. "foo" hs6 = ( 777 .. 'd' ) .. "foo" # same as above hs7 = 5 .. 6 .. 'x' .. 'y' hs8 = (( 5 .. 6 ) .. 'x' ) .. 'y' # same as above echo & "hs5=`{hs5}' is of type { $type (hs5)}" echo & "hs6=`{hs6}' is of type { $type (hs6)}" echo & "hs7=`{hs7}' is of type { $type (hs7)}" echo & "hs8=`{hs8}' is of type { $type (hs8)}"

Code Snippet 19 HSlice : Nested : Nested

hs5=`777 .. d .. foo' is of type HSlice[HSlice[system.int, system.char], system.string] hs6=`777 .. d .. foo' is of type HSlice[HSlice[system.int, system.char], system.string] hs7=`5 .. 6 .. x .. y' is of type HSlice[HSlice[HSlice[system.int, system.int], system.char], system.char] hs8=`5 .. 6 .. x .. y' is of type HSlice[HSlice[HSlice[system.int, system.int], system.char], system.char]

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

import std /[ strformat ] let hs9 = ( 5 .. 6 ) .. ( 'x' .. 'y' ) echo & "hs9=`{hs9}' is of type { $type (hs9)}"

Code Snippet 20 HSlice : Nested, with parentheses : Nested, with parentheses

hs9=`5 .. 6 .. x .. y' is of type HSlice[HSlice[system.int, system.int], HSlice[system.char, system.char]]

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

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

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

Code Snippet 21 Slice : HSlice with same types with same types

s1=`4 .. 6' is of type Slice[system.int] s2=`4 .. 6' is of type HSlice[system.int, system.int]

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

let s1 : Slice [ int ] = 4 .. 'f'

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

Arrays and Sequences #

Array and Sequence Types

Arrays are a homogeneous type, meaning that each element in the array has the same type.

type, meaning that each element in the array has the same type. Arrays always have a fixed length which has to be specified at compile time (except for open arrays).

The array type specification needs 2 things: size of the array, and the type of elements contained in that array using array[SIZE, TYPE] .

The SIZE can be specified as a range like N .. M , or (M-N)+1 for short. The below example shows how an array of 6 integers can be declared and assigned using two different methods.

let i_arr1 : array [ 0 .. 5 , int ] = [ 1 , 2 , 3 , 4 , 5 , 6 ] i_arr2 : array [ 6 , int ] = [ 7 , 8 , 9 , 10 , 11 , 12 ] echo i_arr1 echo i_arr2

[1, 2, 3, 4, 5, 6] [7, 8, 9, 10, 11, 12]

The [ .. ] portion to the right of the assign operator ( = ) is called the array constructor.

Built-in procs low() and high() return the lower and upper bounds of an array. Arrays can also have a non-zero starting index; see the below example.

and return the lower and upper bounds of an array. len() returns the array length.

let i_arr : array [ 6 .. 11 , int ] = [ 1 , 2 , 3 , 4 , 5 , 6 ] echo "min_index = " , low ( i_arr ), ", max_index = " , high ( i_arr ), ", i_arr = " , i_arr , ", length = " , len ( i_arr )

min_index = 6, max_index = 11, i_arr = [1, 2, 3, 4, 5, 6], length = 6

Now, if you need to create a lot of arrays of the type array[6 .. 11, int] , updating their size and/or element type can become painful and error-prone. So the convention is to first create a type for arrays, and then declare variables using that custom type.

Below example is a re-written version of the above example using “typed” arrays.

type IntArray = array [ 6 .. 11 , int ] let i_arr : IntArray = [ 1 , 2 , 3 , 4 , 5 , 6 ] echo "min_index = " , low ( i_arr ), ", max_index = " , high ( i_arr ), ", i_arr = " , i_arr , ", length = " , len ( i_arr )

min_index = 6, max_index = 11, i_arr = [1, 2, 3, 4, 5, 6], length = 6

Inferred Array Types #

Just as you can do let foo = "abc" instead of let foo: string = "abc" , you can let Nim infer the array types too.

import std /[ strformat ] let arr1 = [ 1.0 , 2 , 3 , 4 ] arr2 = [ 'a' , 'b' , 'c' , 'd' ] arr3 = [ "a" , "bc" , "def" , "ghij" , "klmno" ] echo & "arr1 is of type { $type (arr1)} with value {arr1}" echo & "arr2 is of type { $type (arr2)} with value {arr2}" echo & "arr3 is of type { $type (arr3)} with value {arr3}"

arr1 is of type array[0..3, float64] with value [1.0, 2.0, 3.0, 4.0] arr2 is of type array[0..3, char] with value ['a', 'b', 'c', 'd'] arr3 is of type array[0..4, string] with value ["a", "bc", "def", "ghij", "klmno"]

Array elements need to be of the same type #

In the above example, let arr1 = [1.0, 2, 3, 4] worked because Nim inferred 2, 3 and 4 to be floats 2.0, 3.0 and 4.0.

But if you try to mix-and-match types in array elements where they cannot get coerced to the same type, you get an error.

let arr1 = [ 1.0 , 2 , 3 , 4 , 'a' ]

nim_src_PZd0Ji.nim(5, 25) Error: type mismatch: got <char> but expected 'float64 = float'

Two dimensional Arrays #

See Operators for an example of 2-D arrays.

Arrays with enum as length #

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

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

200 two hundred

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

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

type Foo = enum oneHundred , twoHundred , threeHundred proc dict ( f : Foo ) = const fooInt : array [ Foo , int ] = [ 100 , 200 ] echo fooInt [ f ]

nim_src_KdnP9k.nim(11, 31) Error: type mismatch: got <array[0..1, int]> but expected 'array[Foo, int]'

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

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

["he", "reads", "the", "green", "book", "slowly"]

Unchecked Array #

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

var test = alloc0 ( sizeof ( int ) * 4 ) test2 = cast [ ptr UncheckedArray [ int ]] ( test ) test2 [ 1 ] = 5 test2 [ 3 ] = 11 #echo test2[] # doesn't work .. probably a bug? for i in 0 .. 3 : echo test2 [ i ]

Code Snippet 22 : Unchecked Array

0 5 0 11

Sequences are similar to arrays but of dynamic length which may change during runtime (like strings).

In the sequence constructor @[1, 2, 3] , the [] portion is actually the array constructor, and @ is the array to sequence operator.

, the portion is actually the array constructor, and is the array to sequence operator. Just like other Nim assignments, the sequence type does not need to be specified if it is directly assigned a value — the sequence type is inferred by Nim in that case. See Specifying Types for more information.

import std /[ strformat ] let i_seq1 : seq [ int ] = @[ 1 , 2 , 3 , 4 , 5 , 6 ] i_seq2 = @[ 7.0 , 8 , 9 ] echo & "i_seq1 is of type { $type (i_seq1)} with value {i_seq1}" echo & "i_seq2 is of type { $type (i_seq2)} with value {i_seq2}"

i_seq1 is of type seq[int] with value @[1, 2, 3, 4, 5, 6] i_seq2 is of type seq[float64] with value @[7.0, 8.0, 9.0]

One can append elements to a sequence with the add() proc or the & operator,

proc or the operator, pop() can be used to remove (and get) the last element of a sequence.

can be used to remove (and get) the last element of a sequence. Built-in proc high() returns the upper bound of a sequence. low() also works for a sequence, but it will always return 0.

returns the upper bound of a sequence. len() returns the sequence length.

import std /[ strformat ] var i_seq = @[ 1 , 2 , 3 ] echo & "i_seq is of type { $type (i_seq)} with value {i_seq}" echo & "i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}" echo "Adding 100 using `add' .." i_seq . add ( 100 ) echo & " i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}" echo "Adding 200 using `&' .." i_seq = i_seq & 200 echo & " i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}" echo "Popping the last seq element using `pop' .." let popped_elem = i_seq . pop echo & " popped_elem = {popped_elem}" echo & " i_seq = {i_seq}, length = {i_seq.len}, last elem = {i_seq[i_seq.high]}"

i_seq is of type seq[int] with value @[1, 2, 3] i_seq = @[1, 2, 3], length = 3, last elem = 3 Adding 100 using `add' .. i_seq = @[1, 2, 3, 100], length = 4, last elem = 100 Adding 200 using `&' .. i_seq = @[1, 2, 3, 100, 200], length = 5, last elem = 200 Popping the last seq element using `pop' .. popped_elem = 200 i_seq = @[1, 2, 3, 100], length = 4, last elem = 100

Reversing Sequences #

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

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

@[3, 2, 1] @[3, 2, 1] @['c', 'b', 'a']

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

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

cba

Open Arrays #

Open arrays

Open array types can be assigned to only parameters of procedures that receive values of seq or array types.

Open array types are like dynamic arrays, and need to be specified with only the type of the elements that the array is going to contain.

of the elements that the array is going to contain. This array type can be used only for parameters in procedures . So if you do var foo: openArray[int] or let foo: openArray[int] = [1,2,3] (inside a proc or outside), you will get Error: invalid type: ‘openarray[int]’ for var or Error: invalid type: ‘openarray[int]’ for let.

. The openArray type cannot be nested: multidimensional openarrays are not supported.

type cannot be nested: multidimensional openarrays are not supported. Built-in proc high() returns the upper bound of an open array too. low() also works for an open array, but it will always return 0.

returns the upper bound of an open array too. len() returns the open array length.

import std /[ strformat ] proc testOpenArray ( x : openArray [ int ] ) = echo & "x = {x} (type: { $type (x)}, length = {x.len}, max index = {x.high}" let some_seq = @[ 1 , 2 , 3 ] some_arr = [ 1 , 2 , 3 ] echo "Passing a seq .." testOpenArray ( some_seq ) echo "Passing an array .." testOpenArray ( some_arr )

Passing a seq .. x = [1, 2, 3] (type: openArray[int], length = 3, max index = 2 Passing an array .. x = [1, 2, 3] (type: openArray[int], length = 3, max index = 2

If using a Nim devel version older than b4626a220b, use echo repr(x) to print an open array x — plain old echo x or fmt like in the above example won’t work. See $ is not implemented for open arrays for details.

Open arrays serve as a convenience type for procedure parameters so that:

We don’t need to explicitly mention the types of the passed in array (or sequence) values. For example, you can have a proc parameter of type openArray[int] , and have it accept all of these types – array[5, int] , array[1000, int] , seq[int] .. just that the array|seq|openArray elements need to be of the exact same type .. int in this case.

The proc can receive an array of any length as long as they contain the same type elements as defined in the openArray[TYPE] .

Thanks to r3d9u11 from Nim Forum for the below example (ref).

type MyArr = array [ 3 , int ] let a3 : MyArr = [ 10 , 20 , 30 ] a4 = [ 10 , 20 , 30 , 40 ] # Compiler doesn't allow passing arrays with any other length proc countMyArr ( a : MyArr ): int = for i in 0 .. a . high : result += a [ i ] echo "countMyArray(a3) = " , countMyArr ( a3 ) # countMyArray(a4) # will fail # Compiler doesn't allow passing arrays with any other length proc countExplicitArrayType ( a : array [ 3 , int ] ): int = for i in 0 .. a . high : result += a [ i ] echo "countExplicitArrayType(a3) = " , countExplicitArrayType ( a3 ) # countExplicitArrayType(a4) # will fail # Compiler allows passing arrays with different lengths proc countOpenArray ( a : openArray [ int ] ): int = for i in 0 .. a . high : result += a [ i ] echo "countOpenArray(a3) = " , countOpenArray ( a3 ) echo "countOpenArray(a4) = " , countOpenArray ( a4 )

countMyArray(a3) = 60 countExplicitArrayType(a3) = 60 countOpenArray(a3) = 60 countOpenArray(a4) = 100

Above, you can see that countMyArr and countExplicitArrayType procs can accept only int arrays of length 3.

Attempting to pass the 4-element int array a4 to those will give an error like this:

nim_src_gHCsrO.nim(20, 61) Error: type mismatch: got <array[0..3, int]> but expected one of: proc countExplicitArrayType(a: array[3, int]): int expression: countExplicitArrayType(a4)

But countOpenArray proc is not limited by the input array length — It can accept an int array of any length. So countOpenArray(a4) works just fine.

Open Array Limitations #

Modifying Open array variables inside procedures #

From my brief experimentation, you cannot modify the open arrays easily. For example, below does not work. I thought that as a passed in sequence got converted to an openarray type, the reverse should be possible too. But that’s not the case ..

proc foo ( i_oa : openArray [ int ] ) = var i_seq : seq [ int ] = i_oa i_seq . add ( 100 ) echo "input open array: " , i_oa echo "that open array modified to a sequence: " , i_seq foo ( @[ 10 , 20 , 30 , 40 ] )

Above gives the error:

nim_src_dqL4Jb.nim(5, 28) Error: type mismatch: got <openarray[int]> but expected 'seq[int]'

Here is a workaround:

proc foo ( i_oa : openArray [ int ] ) = var i_seq : seq [ int ] for i in i_oa : # copying the input openarray to seq element by element i_seq . add ( i ) i_seq . add ( 100 ) echo "input open array: " , i_oa echo "that open array modified to a sequence: " , i_seq foo ( @[ 10 , 20 , 30 , 40 ] )

input open array: [10, 20, 30, 40] that open array modified to a sequence: @[10, 20, 30, 40, 100]

FIXED $ is not implemented for open arrays #

Thanks to @data-man from GitHub, this issue is now fixed in b4626a220b!

import std /[ strformat ] proc foo ( x : openArray [ int ] ) = echo & "{x}" foo ( [ 1 , 2 , 3 ] )

[1, 2, 3]

Older failure As $ isn’t implement for open arrays, fmt wouldn’t work for these either – Nim Issue #7940. proc testOpenArray ( x : openArray [ int ] ) = echo x Above gives this error: nim_src_ghd2T5.nim(5, 8) Error: type mismatch: got <openarray[int]> but expected one of: proc `$`(x: string): string proc `$`(s: WideCString): string proc `$`[T: tuple | object](x: T): string proc `$`(x: uint64): string proc `$`(x: int64): string proc `$`[T, IDX](x: array[IDX, T]): string proc `$`[Enum: enum](x: Enum): string proc `$`(w: WideCString; estimate: int; replacement: int = 0x0000FFFD): string proc `$`[T](x: set[T]): string proc `$`[T](x: seq[T]): string proc `$`(x: int): string proc `$`(x: cstring): string proc `$`(x: bool): string proc `$`(x: float): string proc `$`(x: char): string expression: $(x) Until that gets fixed, the workaround is to use repr to print open arrays.

Seq vs Array vs Open Array #

Below are few useful comments from this Nim forum thread. While crediting the comment authors, I have taken the liberty to copy-edit those and add my own emphasis.

Stefan_Salewski from Nim Forum # Seqs are generally fine when performance and code size is not critical. As seq data structure contains a pointer to the data elements, we have some indirection, which decreases performance. Arrays are used when number of elements is known at compile time. They offer the maximum performance. Nim Arrays are value (i.e. not reference) objects, living on the stack (see Heap and Stack) if used inside of procs. So a very direct element access is possible. Basically element access is only calculation of an offset, which is the product of element size and index value. If index is constant, then this offset is known at compile time, and so the access is as fast as that of a plain proc variable. An example is a chess program where we have the 64 fields. So we generally would use a array of 64 elements, not a seq. (Well, beginners may use a two dimensional array of 8 x 8, but then we have two indices, which again increases access time. A plain array is OK for this game, as we can increase index by one when we move a piece to the left, and increase index by 8 when we move forward.) OpenArrays is just a proc parameter type that can accept arrays and seqs. Whenever a proc can be used with array or seq parameters, the openArray parameter type should be used. The word “OpenArray” is indeed a bit strange; it was used in the Wirthian languages Modula and Oberon, we have no better term currently. r3d9u11 from Nim Forum # (Provided a very useful example which I expanded upon in Open Arrays).

See tables .

. See critbits .

A tuple type defines various named fields and an order of the fields. The constructor () can be used to construct tuples. The order of the fields in the constructor must match the order in the tuple’s definition.

The assignment operator for tuples copies each component.

Defining tuples #

Assigning the tuple type directly, anonymously . For anonymous tuples don’t use the tuple keyword and square brackets. let person : ( string , int ) = ( "Peter" , 30 ) echo person ("Peter", 30)

Assigning the tuple type directly, with named fields . let person : tuple [ name : string , age : int ] = ( "Peter" , 30 ) echo person (name: "Peter", age: 30)

First defining a custom tuple type, and then using that. This is useful if you don’t wan’t to copy/paste a verbose tuple type like tuple[name: string, age: int] at multiple places.. that approach is also very error-prone and painful if you ever need to refactor that tuple type throughout your code. type Person = tuple [ name : string , age : int ] let person1 : Person = ( name : "Peter" , age : 30 ) # skipping the field names during assignment works too, but this could be less # readable. person2 : Person = ( "Mark" , 40 ) echo person1 echo person2 (name: "Peter", age: 30) (name: "Mark", age: 40)

Alternative way of defining that same custom tuple type. type Person = tuple name : string age : int let person : Person = ( name : "Peter" , age : 30 ) echo person (name: "Peter", age: 30)

Accessing tuple fields #

The notation t.field is used to access a named tuple’s field.

is used to access a named tuple’s field. Another notation is t[i] to access the i ‘th field. Here i must be a constant integer. This works for both anonymous and named tuples.

Anonymous tuples #

import std /[ strformat ] let person : ( string , int ) = ( "Peter" , 30 ) echo & "Tuple person of type { $type (person)} = {person}" echo person [ 0 ] echo person [ 1 ]

Tuple person of type (string, int) = ("Peter", 30) Peter 30

Named tuples #

import std /[ strformat ] type Person = tuple name : string age : int let person : Person = ( "Peter" , 30 ) echo & "Tuple person of type { $type (person)} = {person}" echo person [ 0 ] echo person . name echo person [ 1 ] echo person . age

Tuple person of type Person = (name: "Peter", age: 30) Peter Peter 30 30

Check if a field exists #

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

Named tuples #

type Person = tuple name : string age : int let p : Person = ( "Peter" , 30 ) echo compiles ( p . age ) echo compiles ( p . foo ) # invalid field echo compiles ( p [ 0 ] ) echo compiles ( p [ 1 ] ) echo compiles ( p [ 2 ] ) # invalid field.. tuple has only 2 fields

Code Snippet 23 : Checking if fields exist in a named-tuple variable

true false true true false

Above, the tuple variable p is passed to compiles .

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

type Person = tuple name : string age : int echo compiles ( Person . age ) echo compiles ( Person . foo ) # invalid field echo compiles ( Person [ 0 ] ) # incorrectly returns 'false', compared to compiles(p[0]) in previous snippet

Code Snippet 24 type : Checking if fields exist in a named-tuple

true false false

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

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

nim_src_AVBMhm.nim(10, 12) E