Nim is a strongly-typed, statically-compiled programming language I’ve been playing with for about a year. Here are some things I like about it. All opinions are my own, but you’re welcome to borrow them.
Also, I’m not fighting your favorite language; I’m just trying to convince you to install Nim and play with it.
Press enter or click to view image in full size
Nim isn’t super popular yet, and so sometimes when I’m about to make something with it I get this pit in my stomach wondering “Am I going to regret writing this in Nim instead of a more popular language?” And every time I’m done, the pit is replaced with satisfaction. Nim is so pleasant to use.
I currently use Nim in my budgeting app, Buckets.
Syntax, Syntax, Syntax
Some portion of Earth’s population of humans who dedicate time out of their day to author code meant to be executed by computers express the preference of verbosity as a means of producing clarity and reducing ambiguity in said code.
Others prefer brevity.
I really like how brief Nim lets me be. Take this example program, which displays the number of lines and length of the longest line from stdin:
var
longest = 0
count = 0for line in stdin.lines:
count += 1
if line.len > longest:
longest = line.lenecho "Lines: ", count
echo "Longest: ", longest
Here are some of the syntax features that help make Nim concise:
- Pythonic indentation instead of curly braces. I’m not opposed to curly braces — they’re fine, too.
- Optional parentheses. In the snippet above,
stdin.linesandline.lenare both functions. Also,echois being using without parens. - Uniform Function Call Syntax. It let’s you interchangeably use
len(line)orline.len()— read more about it here. - Macros and Templates. These let you make code that makes code. I’ll give an example below.
My first published Nim library is an argument parser. Through macros and templates, it allows for making very concise command-line argument parsers:
import argparsevar p = newParser("My Program"):
flag("-n", "--dryrun", help="Don't actually do stuff")
command("add"):
flag("-a", "--all", help="Add all files")
arg("dir")
run:
echo "All: ", opts.all
echo "Running on dir: ", opts.dir
command("prefix"):
option("-p", "--prefix", default="backup")
run:
echo "Running with prefix: ", opts.prefixp.run()
Nake
Nake is a Nim library that lets you write Make-like files in Nim. It doesn’t do the same dependency-graph stuff as Make, but if you need a lot of that, you can build it in. Here’s an example Nakefile which you could execute with nake debug-build:
import nakeconst
ExeName = "my_app"
BleedingEdgeFeatures = "-d:testFeature1 -d:testFeature2"task "debug-build", "Build with unproven features":
if shell(nimExe, "c", BleedingEdgeFeatures, "-d:debug", ExeName):
## zip up files or do something useful here?
And since it’s Nim, it’s easy to write cross-platform build scripts with it. You can even branch based on the OS it’s running on.
when defined(windows):
echo "Do windows specific things"
elif defined(linux):
echo "Do linux specific things"
...Which leads me into…
Cross-Compiling
Nim makes it fairly easy to cross-compile (once you find the right documentation). I use macOS for most of my work, but I need to have a Windows VM handy to build some projects. I want the VM to be predictably built, so I’ve created a Nim program that compiles to a single (massive) Windows executable. It’s massive because it includes all the MSI and Zip packages I need to install on my Windows box. Here’s a snippet showing how slurp gathers the installers into the executable:
...const mingwZip = slurp("tmp/mingw64.zip")
const gitExe = slurp("tmp/Git32.exe")
const dllZip = slurp("tmp/dlls.zip")
const nodemsi = slurp("tmp/node.msi")
const yarnmsi = slurp("tmp/yarn.msi")...proc ensure_node() =
try:
if run(@["node", "--version"]) != 0:
raise newException(CatchableError, "Node not installed")
except:
echo "Installing node..."
createDir("C:"/"tmp")
withDir("C:"/"tmp"):
writeFile("node.msi", nodemsi)
discard run(@["msiexec", "/qn", "/passive", "/i", "C:"/"tmp"/"node.msi"])
removeFile("node.msi")
echo "Installed node!"...
I compile this on my Mac, copy it over to the Windows VM and execute it through VirtualBox guest controls. I can now bootstrap my VM with one command!
Speed
The Nim compiler generates C/C++/Obj-C and then compiles that. It generates very fast code.
C-Interoperability
Interoperating with C is very easy. You can use the {.compile.} pragma to pull C files into your code or use some of the import pragmas to link dynamically or statically to C/C++ libraries. Read more here.
You can also produce static/dynamic C libraries from your Nim code.
Caveats
- Some people complain about Nim’s unique “case sensitivity” method. The following identifiers are all equivalent:
someArg,sOmearG,some_arg,some_Arg. In theory, I don’t like this and I hope it eventually goes away. In practice it hasn’t ever been a problem. I didn’t even know this was a thing even after almost a year of use. - Backward-compatibility. Nim is still in active development and there are often breaking changes between releases. This isn’t a complaint — you just have to be aware of it.
- There’s lots of features. Perhaps too many. I imagine many features will get trimmed out before v1.0 is reached.
- I’ve only written solo programs with Nim. I don’t know what it would be like to use Nim in a collaborative environment.
Go play!
In conclusion, go install Nim or try it online. Take it for a spin. Complain a little. Learn a little. Smile a little. It won’t hurt and you might be surprised how pleasant it is!