A Python Substitute? I Tried Out the Best Programming Language You’ve Never Heard Of
Meet Nim: the language with Python-like syntax and C efficiency
A few weeks ago I was browsing through GitHub and encountered a repo that caught my eye. It hosted a project that was written entirely in Nim.
What the hell is Nim? I thought.
Immediately I concluded this was one of the many programming languages that a lot of people use, but I just was the stupid guy who didn’t know it. But rather than putting it aside, I decided: Let’s learn a bit about this.
I then had two major realizations:
- No, this language is not used by a lot of people.
- But maybe it should be.
So here goes a bit about my experience with Nim, a quick programming tutorial, and why the language seems extremely promising to me.
Show Me the Code!
Here’s a useless program I wrote in Nim:
Looks pretty clean. It also feels so simple that you can probably figure out what it does with no effort, even though you might have never heard of Nim before. (Hint: It prints
num: 5 i: 5.)
So let’s break down what seems familiar here:
var and some use
let, JS and Nim both allow declarations using either of the two. It’s important to note that they do not have the same meaning in both languages, however. But more on that later.
To mark a new block in Nim, we use a colon followed by an indented line. That’s Python right there.
Both loops, as well as the if statement, look like they were plucked right out of Python. In fact, everything from line 5 onwards is actually valid Python (assuming we had an
echo function defined).
So yes, a lot of the keywords and operators from Python are also valid in Nim, such as
or , etc.
Until now, there’s nothing special about Nim at all. It just looks like a worse version of Python (syntax-wise), since we need to use
But what if I told you this: Nim is a statically typed language that runs almost as fast as C.
Oh, now we’re talking.
A Friendly Race
Before diving a little deeper into the Nim syntax (especially the statically typed part, which we still haven’t seen), let’s try to back up the claims about its speed. To do so, I wrote a program to naively (i.e., without dynamic programming) compute the n-th Fibonacci number in Nim, Python, and C.
In order to keep things fair, I standardized the implementation based on the suggested Leetcode solution for this problem (Approach 1) and made sure to stick to it as much as possible across the three languages.
EDIT: People keep telling me about LRU Cache. I am aware of it. However, the implementations in all the languages could be improved with memoization. The point here is just to pick a standard implementation and use it, not try to optimize as much as possible. Hence I picked a naive implementation.
To time the execution, I used the “real” value from the output of
time in the Bash shell.
Here are the results for computing the 40th Fibonacci number:
Yeah, that happened.
Nevertheless, all the code I’ll run through in this article is available on GitHub, including instructions on how to try this experiment out.
Edit: It has come to my attention that by simply passing a flag to the Nim compiler I could have improved its speed of execution by over 10x. Some have done the tests and found Nim to be even faster than C. This happens because, as stated in the Official Docs: “By default the Nim compiler generates a large amount of runtime checks aiming for your debugging pleasure. With
-d:releasesome checks are turned off and optimizations are turned on.”
The graph will not be updated for the sake of finality, but I thought this note was important. Refer to this issue for more information.
So why is Nim so much faster than Python?
Well, I would say there are two main reasons. Simplifying heavily, these are:
- Nim is compiled while Python is interpreted (I know it’s a debate, but I’m pulling this from the Docs to keep it short). This means that when a Python program is run, there’s more work being done than just running the program, as it needs to be interpreted before it can actually execute. This generally makes languages much slower.
- Nim is statically typed. While the example I showed earlier didn’t have a single type declaration or annotation, we will see later that it is indeed a statically typed language. In the case of Python, which is dynamically typed, the interpreter needs to do a lot more work to figure out and appropriately handle types, which slows down execution.
Faster to Run, Slower to Write
Here’s what the Python Docs have to say about interpreted languages:
“Interpreted languages typically have a shorter development/debug cycle than compiled ones, though their programs generally also run more slowly.”
This sentence is a good summary of the tradeoff between Python and C, for example. Anything you can do with Python you can also do with C, and your program will run many orders of magnitude faster.
However, you will be spending a lot more time writing and debugging your code in C, as well as it will be longer and less readable. And that’s why C isn’t as trendy anymore and Python is so popular. To put it simply: Python is “easy” (comparatively, of course).
So, if Python is on one end of the spectrum and C is on the other, Nim is trying to be somewhere in the middle: somewhat fast and somewhat easy? Something like that.
What makes Nim stand out for me, however, is that at first glance it feels like it has minimized the tradeoff. In other words, it is much faster than Python, but not equally harder to program in, like C (or so it feels like upon brief inspection).
To illustrate this point, let’s look at the code from our Fibonacci experiment.
Here’s the C code:
While Nim has this weird
proc thing and, dear God, uses
= to declare functions (or procedures, as they’re called), it’s still much cleaner than C.
Hence, maybe it’s a worthy tradeoff? A little harder to write than Python, but tens of times faster — I could live with that.
Here’s a brief overview of some key points about Nim’s syntax:
Variables are declared with
let , or
let is a different story.
var in terms of scope,
let in Nim denotes a variable whose value cannot change after initialization. This is apparently similar to Swift, I’ve now been told.
But isn’t that a constant?
Well, in Nim the distinction is as follows:
const, the compiler must be able to determine the value at compile time, whereas
let can be determined at runtime.
The documentation offers this example:
const input = readLine(stdin) # Error: constant expression expectedlet input = readLine(stdin) # works
Additionally, you can also declare variables like this:
a = 1
b = 2
c = 3
x, y = 10 # Both x, y are assigned to 10
Functions in Nim are called procedures, and their declaration is done like this:
proc procedureName(parameterName: parameterType):returnType =
Given that the language looks like Python in a lot of ways, procedures definitely stand out as being a little weird when you first see them.
= instead of
: is especially odd. However, it looks a little better in one-liner procedures:
proc hello(s: string) = echo s
Also, you’re able to return from functions like this:
proc toString(x: int): string =
if x < 0: “negative”
elif x > 0: “positive”
It feels like you should still do
return result, but
result is not a variable — it’s a keyword. The above snippet is perfectly valid Nim.
And you’re also able to overload procedures:
It’s a lot like Python.
# if true:# while true:# for num in nums:
For iterating over a range, instead of a range you can use
countup(start, finish), or
countdown(start, finish) . Or you can simplify the whole thing and use:
for i in start..finish.
Printing and getting user input
let input = readLine(stdin)
When comparing to Python,
readLine(stdin) is the equivalent of
echo is the equivalent of
echo can be used with or without parentheses.
My goal here is to give you a taste of Nim, not go through their entire manual. As such, I think I’ll stop here for plain syntax and skim over some additional features next.
Nim is not object-oriented but has minimalistic support for objects. They are not as neat as Python classes, however.
Nim supports macros and metaprogramming, and, in fact, seems to heavily emphasize it. An entire part of their three-part tutorial series is dedicated to it.
Here’s a quick example:
import macros macro myMacro(arg: static[int]): untyped =
myMacro(1 + 2 * 3)
The basic types in Nim are:
These are also valid:
int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64
Additionally, strings are mutable in Nim, unlike in Python.
You probably already saw my comments in Python syntax above, but unlike Python, multiline comments also make use of the hash symbol (followed by
# a comment#[
From the Nim website:
That’s quite cool, although I’m not sure how many people would actually use it. But if you want to play Browser Snake written in Nim, you can do so. This time I didn’t build it, though.
Instead of defining a
proc, one can also define an
iterator. However, Nim iterators are actually more like Python generators. Here’s an example:
iterator countup(a, b: int): int =
var res = a
while res <= b:
Case and underscore indifference
Nim is case- and underscore-insensitive (except for the first character).
helloWorld are different, but
hello_world are all the same, making this valid:
proc my_func(s: string) =
Maybe you read the title and said to yourself: Uh, I’ve heard of Nim or I’ve used Nim!
In that case, hey, I’m happy for you. However, I did try to get a bit of info on the popularity of the language, and it surely isn’t that high.
For example, Nim wasn’t even mentioned on the 2020 Stack Overflow Survey. I couldn’t find any jobs for Nim developers on LinkedIn (Location set to Worldwide), and the Stack Overflow tag for the language has only 349 questions. (Compare that to Python’s ~1,500,000, or those of a newer language, like Swift’s 270,000.)
Thus, it’s quite fair to assume most developers haven’t used it and that many have not ever even heard the name Nim.
A True Python Alternative?
I’ll be honest with you, I find Nim pretty cool.
To write this article, I went over the bare minimum, so haven’t gone too deep into it yet, but I can see myself using it for something in the future.
However, while the basic syntax is very similar to Python, it gets more complicated quite fast, which I’m sure would throw off a lot of the Python user/developer base.
Personally, I am a big fan of Python but also of statically typed languages, so for me, the performance improvement in certain cases would more than compensate for the added verbosity.
Then, through writing this article I realized: What about Go?
I’m sure many of you were thinking just that while reading, and it is a valid point. As much as the syntax of Nim might be closer to that of Python, it really is competing in the space of performant-yet-nicer-than-C++ languages, which is currently led by Go.
Fun fact: I also sneakily ran the speed test with Go. For
fibonacci(40) specifically, it was as fast as C.
So, can Nim compete with Python? I highly doubt it. We’re seeing a trend of computers getting faster and programming getting easier, to the point that even if Nim offers a good tradeoff, as I pointed out, I don’t think it’s enough to take on the clean and versatile Python.
Edit: I spoke to one of the Nim Core Devs, who told me he believes Nim is more suitable for people transitioning from C++ than people transitioning from Python.
However, can it compete with Go? Maybe (if Google wasn’t behind Go). The syntax is friendly, the language is powerful, and it has better support for C/C++ features than Go offers (e.g. macros and overloading).
Maybe that’s what I should look into next?
Thanks for reading!