What is special about Nim?2015-01-01 · Nim · Programming
The Nim programming language is exciting. While the official tutorial is great, it slowly introduces you to the language. Instead I want to quickly show what you can do with Nim that would be more difficult or impossible in other languages.
I discovered Nim in my quest to find the right tools to write a game, HookRace, the successor of my current DDNet game/mod of Teeworlds. Since I'm busy with other projects for now, this blog is now officially about Nim instead, until I find time to continue developing the game.
Easy to get running
Ok, this part is not exciting yet, but I invite you to follow along with the post:
If you want to do so, get the Nim compiler. Save this code as
hello.nim, compile it with
nim c hello and finally run the binary with
./hello. To immediately compile and run, use
nim -r c hello. To use an optimized release build instead of a debug build use
nim -d:release c hello. With all of these settings you will see the following output:
H He Hel Hell Hello Hello Hello W Hello Wo Hello Wor Hello Worl Hello World
Run regular code at compile time
To implement an efficient CRC32 procedure you need a lookup table. You could compute it at runtime or write it into your code as a magic array. Clearly we don't want any magic numbers in our code, so we'll do it at runtime (for now):
Great, this works and we get
414FA339 as the output. But it would be even better if we could compute the CRC table at compile time. This is as easy as it gets in Nim, instead of the current crc32table creation we use:
Yes, that's right: All we had to do was replace the
var with a
const. Beautiful, isn't it? We can write the exact same code and have it run at runtime and compile time. No template metaprogramming necessary.
Extend the language
Templates just replace their invocations with their code at compile-time. We can define our own loops like this:
So the compiler transforms the times-loop to this regular for-loop:
If you're curious about the
10.times: syntax, it's just a regular call to
10 as the first parameter and the following indented block as the second parameter. Instead you could also write
times(10):, see Unified Call Syntax.
Or initialize sequences (variable sized arrays) more comfortably:
Macros go a step further and allow you to analyze and manipulate the AST. There are no list comprehensions in Nim, for example, but it's possible to add them to the language by using a macro. Now instead of this:
You can use the future module and write:
Add your own optimizations to the compiler
Instead of optimizing your code, wouldn't you prefer to make the compiler smarter? In Nim you can!
This (pretty useless) code can be sped up by teaching the compiler two optimizations:
In the first term rewriting template we specify that
a * 2 can be replaced by
a + a. In the second one we specify that
ints in a multiplication can be swapped if the first is an integer literal, so that we can potentially apply the first template.
More complicated patterns can also be implemented, for example to optimize boolean logic:
s gets optimized to
r gets optimized to
x in 2 successive pattern applications and
q ends up as
If you want to see how term rewriting templates can be used to avoid allocations with bigints, look for the templates starting with
opt in the bigints library:
Bind to your favorite C functions and libraries
Since Nim compiles down to C, foreign function interfaces are fun.
You can easily use your favorite functions from the C standard library:
Or use your own code written in C:
Or any library you please with the help of c2nim:
Control the garbage collector
To achieve soft realtime, you can tell the garbage collector when and for how long it is allowed to run. The main game logic can be implemented like this in Nim, to prevent the garbage collector from causing stutters:
Type safe sets and arrays of enums
Often you want a mathematical set over values you defined yourself. Here's how you do this in type-safe way:
You can't accidentally add a value of another type. Internally the set works as an efficient bitvector.
The same is possible with arrays, indexing them with your enum:
Unified Call Syntax
This is just syntactic sugar, but it's definitely nice to have. In Python I always forget whether
append are functions or methods. In Nim I don't have to remember, because there is no difference. Nim uses Unified Call Syntax, which has now also been proposed for C++ by Herb Sutter and Bjarne Stroustrup.
I made some measurements on my machine when the benchmark was first published (Linux x86-64, Intel Core2Quad Q9300 @2.5GHz, state of 2014-12-20):
|Lang||Time [ms]||Memory [KB]||Compile Time [ms]||Compressed Code [B]|
Compressed code size with
gzip -9 < nim.nim | wc -c. Removed unused functions in Haskell. Compile times are clean compiles, if you have a nimcache with the standard library precompiled it's only 323 ms for Nim.
Another small benchmark I did, calculating which numbers of the first 100 million are prime, in Python, Nim and C:
Python (runtime: 35.1 s)
Nim (runtime: 2.6 s)
C (runtime: 2.6 s)
We define a
Data type that we use to pass data from the server to client. The
printInfo procedure will be called with this
data and display it. Compile this with
The server delivers the main website. It also delivers the
client.js, by compiling and reading the
client.nim at compile time. The logic is in the
/visitors handling. Compile and run with
nim -r c server and open http://localhost:5000/ to see the code in effect.
You can see our code in action on the Jester-generated site or inline here:
I hope I could pique your interest in in the Nim programming language.
Note that the language is not completely stable yet. Especially with the more obscure features you may run into bugs. But on the bright side, Nim 1.0 is supposed to be released within the next 3 months! So now is the perfect time to get started with learning Nim.
Bonus: Since Nim compiles to C and only depends on the C standard library you can deploy it nearly everywhere, including x86-64, ARM and Intel Xeon Phi accelerator cards.
Thanks to Andreas Rumpf and Dominik Picheta for proof-reading this post.