Nim in Action: Book errata for Nim v1.0.0
The Nim programming language recently hit the 1.0.0 milestone and I decided to pick it up. Since Manning’s Nim in Action came back in 2017, it naturally targets an older version of Nim. Fortunately, there haven’t been big user-visible changes to Nim over these two years so it’s still possible to use this book to learn. That said, as is inevitable with a new language that’s still under development, some things have changed.
I am compiling an errata of sorts as I work my way through the book. All page numbers correspond to the PDF/print version of the book. While a lot of compiler error messages have changed, I’ll be highlighting only the important ones.
Chapter 1
Listing 1.7
import sequtils, future, strutils
let list = @["Dominik Picheta", "Andreas Rumpf", "Desmond Hume"]
list.map(
(x: string) -> (string, string) => (x.split[0], x.split[1])
).echo
gives a warning during compilation:
Warning: Use the new 'sugar' module instead; future is deprecated [Deprecated]
Simply changing future
to sugar
fixes it without requiring any other change to the code in this listing.
Chapter 2
Section 2.2.2
Page 32
I added the echo at the top of fillString’s body, in order to show you that it’s executed at compile time. Try compiling the example using Aporia or in a terminal by executing nim c file.nim.
Be aware that Aporia IDE is now obsolete. The project’s page mentions VSCode as an alternative. I personally use emacs with nim-mode and find it more than adequate.
There are plenty of references to Aporia throughout the book. I won’t highlight them in this post any further.
Page 33
For more information about Nim’s conventions, take a look at the “Style Guide for Nim Code” on GitHub: https://github.com/nim-lang/Nim/wiki/Style-Guide-for-Nim-Code.
The Style Guide page has now moved to:
https://nim-lang.org/docs/nep1.html
Section 2.2.3
Page 38
doAssert getUserCity("Damien", "Lundi") == "Tokyo"
doAssert getUserCity(2) == "New York
It might be confusing to see that the book uses assert
for previous code snippets but for this one, suddenly switches to doAssert
.
The difference between assert
and doAssert
is that assert
is ignored by the nim compiler when using the -d:release
or --assertions:off
command line switches, whereas doAssert
isn’t affected by them. Source. This is also explained later in the book in Chapter 3 (page 76).
Section 2.3.1
Page 40
Compilation will fail with “Error: index out of bounds.”
It’s still a compile-time out of bounds error but the error message is now worded differently:
Error: index 5000 not in 0 .. 2
Section 2.3.2
Page 41
“You should see that your program crashes with the following output:
Traceback (most recent call last)
segfault.nim(2) segfault
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
This still segfaults but the error message is now different and definitely more helpful:
segfault.nim(2) segfault
/usr/local/Cellar/nim/1.0.0/nim/lib/system/fatal.nim(39) sysFatal
Error: unhandled exception: index out of bounds, the container is empty [IndexError]
Page 42
assert list[0] == nil
This construct now seems to be deprecated for strings. I get this compile-time error:
seq.nim(3, 16) Error: 'nil' is now invalid for 'string'; compile with --nilseqs:on for a migration period; usage of '==' is a user-defined error
Changing this to…
assert list[0] == ""
…works. So strings are now automatically initialised to “” rather than nil.
Chapter 3
This chapter covers a lot of ground and by the end of it you have a working group chat server and a client that connects to it. I only ran into the following trivial issues.
Listing 3.16
import asyncdispatch, asyncfile
var file = openAsync("/etc/passwd")
let dataFut = file.readAll()
dataFut.callback =
proc (future: Future[string]) =
echo(future.read())
asyncdispatch.runForever()
On macOS 10.13.6 (High Sierra), this ended in a runtime error after the callback finishes displaying the file’s content:
/usr/local/Cellar/nim/1.0.0/nim/lib/pure/asyncdispatch.nim(1874) runForever
/usr/local/Cellar/nim/1.0.0/nim/lib/pure/asyncdispatch.nim(1569) poll
/usr/local/Cellar/nim/1.0.0/nim/lib/pure/asyncdispatch.nim(1286) runOnce
Error: unhandled exception: No handles or timers registered in dispatcher. [ValueError]
Error: execution of an external program failed: '/Users/deepakg/proj/async_file
I still haven’t figured out if the author deliberately intended to demonstrate something or if something changed in a version of Nim after the book came out. That said, the next listing (3.17) that uses the {.async.}
pragma with the await
keyword works just fine.
Listing 3.17
import asyncdispatch, asyncfile
proc readFiles() {.async.} =
var file = openAsync("/home/profile/test.txt", fmReadWrite)
let data = await file.readAll()
echo(data)
await file.write("Hello!\n")
file.close()
waitFor readFiles()
It might come as a surprise that the echo(data)
call never prints anything, even when the file already exists and contains text. Additionally, the file contents are reset to Hello!
after running the code.
This is because fmReadWrite
file mode clears the existing data during the openAsync
call. If you want to preserve and display the file contents, use fmReadWriteExisting
file mode instead.
Section 3.5.3
Page 92
On UNIX-like operating systems such as Linux and Mac OS, the telnet application should be available by default.
On macOS 10.13 (High Sierra) that’s not the case. But it’s easily remedied by using brew to install it.
Chapter 4
This chapter is a whirlwind tour of Nim’s standard library. I did not find any issues. All listings compiled and ran with Nim 1.0.0 without any problems.
Chapter 5
I did not find any issues. This chapter is about Nim’s package manager: Nimble. I was able to follow along and create a toy package and use it. I ignored section 5.7 (publishing a package to github), but it should just work.
Chapter 6
While chapter 3 gave a taste of concurrency in Nim and also touched upon parallelism (spawn
), this chapter offers a deeper look into parallelism in Nim through its threading capabilities.
Section 6.2.3
Page 159
The ways exceptions behave in separate threads may be surprising. When a thread crashes with an unhandled exception, the application will crash with it. It doesn’t mat- ter whether you read the value of the FlowVar or not.
FUTURE VERSIONS This behavior will change in a future version of Nim, so that exceptions aren’t raised unless you read the value of the FlowVar.
This hasn’t changed as of Nim 1.0.0
Listing 6.20
Page 172
responses.add(spawn parseChunk(buffer[0 .. <chunkLen])) oldBufferLen = readSize - chunkLen
buffer[0 .. <oldBufferLen] = buffer[readSize - oldBufferLen .. ^1]
var mostPopular = newStats()
for resp in responses:
let statistic = ^resp
if statistic.countViews > mostPopular.countViews:
mostPopular = statistic
The thing that puzzled me a little about this listing was the lack of an explicit sync()
call (like one in Listing 6.7 on page 157). We spawn multiple threads in a loop to run parseChunk
but we don’t wait for them to finish. This is because the for resp in responses
loop a couple of lines below, implicitly waits for each thread to finish when it retrieves the result using ^resp
.
Listing 6.21
Page 173
import threadpool
var counter = 0
proc increment(x: int) =
for i in 0 .. <x:
var value = counter
value.inc
counter = value
spawn increment(10_000)
spawn increment(10_000)
sync()
echo(counter)
Now gives a compile-time deprecation warning.
shared_memory_race_condition.nim(6, 17) Warning: < is deprecated [Deprecated]
This is easily fixed by changing 0 .. <x
to 0..<x
or 0 ..< x
in the listing (no space between .. and <). Listing 6.23 and 6.26 have the exact same issue as they build upon listing 6.21.
Chapter 7
This is another pretty substantial chapter and walks us through creation of a web application with a sqlite database backend.
Section 7.2
Page 188
You’ll also need to add bin = @[“tweeter”] to the Tweeter.nimble file to let Nimble know which files in your package need to be compiled.
When you run nimble (v.0.11.0) that ships with Nim v.1.0.0, it’ll ask you to choose your package type:
Prompt: Package type?
... Library - provides functionality for other packages.
... Binary - produces an executable for the end-user.
... Hybrid - combination of library and binary
... For more information see https://goo.gl/cm2RX5
Select Cycle with 'Tab', 'Enter' when done
Choices: library
> binary <
hybrid
The default selection for package type is library
but if you select binary
, the nimble file you get will already have a bin = @[“Tweeter”] entry. Also, nimble now creates src/Tweeter.nim for you with some example code in it. This is different from lowercase tweeter.nim that you create by hand in the book.
To avoid cognitive dissonance while following along with the book, here is what I did:
- renamed Tweeter.nim to tweeter.nim
- changed the auto-generated line
bin = @["Tweeter"]
to what’s in the book, i.e.bin = @[tweeter]
.
With these changes, I was able to use the nimble command exactly as it is in the book: nimble c -r src/tweeter
and move forward.
Listing 7.13
Page 196
database.db.exec(sql"INSERT INTO Message VALUES (?, ?, ?);",
message.username, $message.time.toSeconds().int, message.msg)
This line now causes a deprecation warning because of the toSeconds()
method:
src/database.nim(25, 52) Warning: toSeconds is deprecated [Deprecated]
Changing it to toUnix()
makes it go away:
database.db.exec(sql"INSERT INTO Message VALUES (?, ?, ?);",
message.username, $message.time.toUnix().int, message.msg)
Listing 7.14
Page 197
Likewise, the corresponding fromSeconds()
call under the findMessages
proc:
result.add(Message(username: row[0],
time: fromSeconds(row[1].parseInt), msg: row[2]))
needs to change to fromUnix()
result.add(Message(username: row[0],
time: fromUnix(row[1].parseInt), msg: row[2]))
This listing produces another couple of deprecation warnings:
for i in 0 .. <usernames.len:
whereClause.add("username = ? ")
if i != <usernames.len:
whereClause.add("or ")
src/database.nim(87, 17) Warning: < is deprecated [Deprecated]
src/database.nim(89, 13) Warning: < is deprecated [Deprecated]
We’ve already encountered the first one before (listing 6.21), the second one can be addressed by writing the if i != <usernames.len:
statement a little differently:
for i in 0 ..< usernames.len:
whereClause.add("username = ? ")
if i != usernames.len - 1:
whereClause.add("or ")
Listing 7.21
Page 205
The following code:
<span>${message.time.getGMTime().format("HH:mm MMMM d',' yyyy")}</span>
now gives a deprecation warning:
src/views/user.nim(35, 52) Warning: getGMTime is deprecated [Deprecated]
Replacing getGMTime
with utc
fixes it.
<span>${message.time.utc().format("HH:mm MMMM d',' yyyy")}</span>
Chapter 8
This chapter explores Nim’s Foreign Function Interface (FFI) and shows how you can easily wrap not just standard C library functions but also external C libraries. It concludes with a quick look at Nim’s JavaScript backend that allows you to compile Nim code to JavaScript and interface with Browser’s DOM. I did not run into any issues with this chapter.
Chapter 9
This chapter is a tour of Nim’s meta-programming capabilities.
Page 250
import macros
type
Person = object
name: string
age: int
static:
for sym in getType(Person)[2]:
echo(sym.symbol)
Now produces a deprecation warning.
hello_meta.nim(10, 14) Warning: Deprecated since version 0.18.1; All functionality is defined on 'NimNode'.; symbol is deprecated [Deprecated]
/usr/local/Cellar/nim/1.0.0/nim/lib/system.nim(3431, 32) Warning: Deprecated since version 0.18.1;o Use 'strVal' instead.; $ is deprecated [Deprecated]
Initially I wrote this snippet using the suggestions above and it compiled without warnings:
import macros
type
Person = object
name: string
age: int
static:
for sym in getType(Person)[2]:
echo(sym.NimNode.strVal)
However, I did get a “Hint” during compilation:
hello_meta.nim(14, 13) Hint: conversion from NimNode to itself is pointless [ConvFromXtoItselfNotNeeded]
Which made me realise that sym
here was already a NimNode. This was the final version of my code that produced no warnings on hints:
import macros
type
Person = object
name: string
age: int
static:
for sym in getType(Person)[2]:
echo(sym.strVal)
Section 9.2
Page 255
This snippet fails to compile:
template `!=` (a, b: untyped) =
not (a == b)
doAssert(5 != 4)
hello_template.nim(4, 12) Error: expression 'true' is of type 'bool' and has to be discarded
Adding an explicit untyped
as the template’s return type fixes it:
template `!=` (a, b: untyped): untyped =
not (a == b)
doAssert(5 != 4)
Listing 9.3
Page 263
The Nim AST for 5 * (5 + 10) displayed using an indentation-based format
StmtList
Infix
Ident !"*"
IntLit 5
Par
Infix
Ident !"+"
IntLit 5
IntLit 10
This looks a little different with Nim 1.0.0
StmtList
Infix
Ident "*"
IntLit 5
Par
Infix
Ident "+"
IntLit 5
IntLit 10
Note the missing exclamation signs after Ident
.
Listing 9.6
Page 269
Save this code into configurator.nim and compile the file. You’ll see the following among the output:
Ident !"MyAppConfig"
StmtList
Call
Ident !"address"
StmtList
Ident !"string"
Call
Ident !"port"
StmtList
Ident !"int"
Again, the output looks a little different with Nim 1.0.0 (no !
after Ident). I won’t mention this specific issue any further.
Ident "MyAppConfig"
StmtList
Call
Ident "address"
StmtList
Ident "string"
Call
Ident "port"
StmtList
Ident "int"