Nim: First impressions

As someone who writes software for a living, I think it's a good idea to learn a new language/technology every once in a while. It makes you think differently from what you've been focusing on in your normal 9-to-5.

In that spirit, I started playing around with Nim last weekend. Nim is a neat little language that has been around for a bit more than 10 years, but has been (surprisingly) under the radar, at least if you compare it to how much publicity Go and Rust generate.

The promise looks quite nice - high performance, (optional) garbage collection, dependency-free binaries, compilation to JavaScript (!), and a clean syntax that at times reminds me of Python.

After working through the official tutorial and Nim by Example over the weekend, I've come to really like the language. There are some quirks that I wish weren't there, but I'll try to summarize my first impressions in the following sections.

The Compiler

If you're used to writing code in an interpreted language, "fighting the compiler" can be a thing.

But I'm pleased to report that the Nim compiler, while pedantic like all others, is actually quite helpful. The error messages are (for the most part) concise and tell you exactly what you should do to turn your code into a binary. And it's always nice to catch possible runtime errors at compile time.

For example, consider the following snippet that asks the user to enter a number.

import strutils

echo "What's your number? "

var number = strutils.parseInt(readLine(stdin))

case number:
  of 1:
    echo "One"
  of 2:
    echo "Two"

This fails to compile with the error message not all cases are covered, which means that the compiler noticed that your case statement is not checking all the possible cases and that this may cause unexpected behavior at runtime.

Kind-of simple

Nim looks small and simple on the surface, but it's a fairly advanced language. There are features that let you do extremely funky things. You probably won't end up using those features in daily use (well, depending on what you're using it for), but you know that they are there.

I would argue that this is nice because the language feels approachable. It feels like you can hold the entire language in your head (even though that may not be the case). I don't feel the urge to hide in a corner on seeing Nim code as a beginner to the language. At the same time you know it in the back of your head that when you need to do advanced things, you can.

Dependency-free binaries

Coming from a Python world, this is huge. The simplest Python deployments can still be fairly tricky to get right. There's the whole dance of virtualenvs, installing dependencies, n-number of decisions you need to take before even starting. And god help you if you need to install something that's not pure Python.

Nothing is simpler than compiling the project and putting one binary on the server and seeing things work.

Compile-time constants

const value = doSomethingExpensive()

value is set here at compile-time. Of course, if doSomethingExpensive cannot be worked out at compile-time, the compiler will shout at you. But when you're looking to reduce every single bit of runtime overhead, this is extremely valuable.

Productivity

It was fascinating to observe that after spending effectively only a few hours with the language, I felt like I was in a position to write working code. I've had multiple moments writing Nim code when you write something and then look at it and then go "was that it?".

The last time something like this happened was when I started learning Python. It was astonishing that a programming language could put you in a position where you can write working code so quickly.

Variable assignment

var x, y = 3

This sets the value of both x and y to be 3, which seems slightly confusing. Go does not compile something similar, complaining that there are 2 variables but 1 value.

To be honest, this is not a huge deal, but I can definitely see myself debugging bugs caused by this behavior.

Methods

From methods:

Adding a method to a class the programmer has no control over is impossible or needs ugly workarounds.

This is mentioned under the section "disadvantages". I don't think this is a disadvantage. I would actually argue that this keeps the programmer from doing funky things that harm readability.

I once spent 2 days trying to debug an issue in a Ruby on Rails codebase where a third-party gem was monkey-patching some functions defined in ActiveRecord. This was a classic example of modifying something you're not supposed to modify.

Module import semantics

So far this has been my biggest complaint with Nim.

Consider this simple program.

import strutils

let s = "Hello, World!"

echo s.split()
echo strutils.split(s)
echo split(s)

The last three lines are all doing the same thing. So not only is the split() function suddenly available on strings, but is also (optionally) a part of the local namespace. It's difficult to say if split is a part of strutils or a built-in function or a function defined on string types (if you didn't notice the import strutils).

After writing Python code for slightly more than 10 years, I have "explicit is better than implicit" plastered all over my brain. So such behavior makes me a little uneasy.

From what I understand, this is a consequence of Uniform Function Call Syntax, but IMHO this feels like a step back in terms of being able to look at a piece of code and immediately make sense out of it.

But the Nim developers know this complaint. There's already a Github issue, and the forum thread even has a proposal that might be implemented in one of the next releases. So I'm hopeful!


Overall, I like Nim. If it's not painfully obvious, this article barely scratched the surface. There is so much more to the language that I haven't written about here for the sake of keeping this article short.

There are some design-things which I either don't get or don't agree with. But on the other hand it provides many nice features without necessarily compromising on speed or introducing complexity. My first attempt at picking up one of the new systems-programming languages was at Rust, but it introduced so much complexity that it was overwhelming.

Nim feels approachable, and gives you the feeling that it's powerful for when you need something more than just simple.

Given that I've spent less than a week working with it and still like it so much, I can definitely see myself writing more Nim code in the future.