Nim Language Highlights

In the last post I covered some things I’ve found compelling about nim’s big picture.

Today I’ll do the same but with the language itself. If you’re still on the fence about what the big deal is, let me show you a few things I found interesting.

Let’s get scrolling.

Templates

A template is a function which can help you reduce boilerplate and write cleaner code. It’s kinda like a preprocessor in c, except type-safe.

flip.nim
# define a template like a function
template safely(stuff: untyped) =
    try:
        stuff
    except:
        echo "(╯°□°)╯︵ ┻━┻"

let msg = "i am happy!"

# call the template passing a block as the parameter
safely:
    echo msg[0]
    echo msg[2..3]
    echo msg[5..1000]
$ nim c -r flip.nim
i
am
(╯°□°)╯︵ ┻━┻

Fun fact from the manual: the != operator is a template which rewrites a != b to not (a == b). Neat eh?

Macros

When you ask about the killer features of nim, many people will say macros.

Macros operate at the AST level. By evaluating the inbound code syntax, you can perform branching logic, augment it, or even rewrite it entirely. You can use macros to define DSLs or introduce amazing language features like pattern matching and interfaces.

please.nim
import macros

macro theMagicWord(statments: untyped): untyped =
    result = statments
    for st in statments:
        for node in st:
            if node.kind == nnkStrLit:
                node.strVal = node.strVal & " Please."

echo "Hey!"
theMagicWord:
    echo "Grab me a beer!"
$ nim c -r please.nim
Hey!
Grab me a beer! Please.

Macros are a big topic and best left for a dedicated post; don’t wait for me though:

Flexible Identifier Names

Alright. Grab your torch & pitchfork.

Identifiers, such as variable names and functions, have flexible names. It’s case insensitive (except for the first letter) and underscores are optional.

goodday.nim
proc goodDayToYou(firstName: string) =
    echo "Good day to you " & firstname & "."

goodDayToYou("Joe Camel")
good_day_to_you("Medusa")
$ nim c -r goodday.nim
Good day to you Joe Camel.
Good day to you Medusa.

Did you catch the firstname vs firstName in the function above? I bet you saw it and laughed at me. You know who else saw it? The compiler; and it knew what I meant.

Is it a big deal? Between nimgrep and nimsuggest (via IDE plugins), discoverability isn’t an issue. You can also rename functions and constants from external c / c++ libraries as they come in, so that’s not a problem either.

It’s just quirky and oddly satisfying when it saves your butt.

EDIT #1: Dominik Picheta points out that the practical purpose for this feature is to have freedom from the naming choices of your dependencies. You’re Team Camel but they’re Team Snake? No problem. Now you can stop avoiding eye contact with fellow devs at conferences.

Flexible Functions Calls

Let’s define a function which clips text at a ridicu…

trunk.nim
import strutils

proc truncate(value: string, length = 6, ellipsis = "..."): string =
    if value.len > length:
        value[0..length - 1] & ellipsis
    else:
        value

How do we invoke it?

Call It Like A Function

As you’d expect, you can call the function like this:

echo truncate("hello world")
# hello ...

Turns out the story doesn’t end here.

Call It Like A Method

The first parameter is a string, so in any scope where the truncate function is available, it is now accessible on any instances of string. Like extensions in swift & c# or mixins in ruby.

echo "hello world".truncate(4)
# hell...

Now you can operate in a more fluent-style where you chain functions together like a pipe.

TIL this has a name: UFCS.

Parentheses Can Be Optional

Parentheses are optional as long as there is no ambiguity.

echo "hello WORLD ".toLowerAscii.truncate(4).strip
# hell...

echo "hello WORLD ".toLowerAscii.truncate.strip
# hello ...

echo " hello WORLD ".toLowerAscii.truncate 4.strip
# Error: type mismatch: got <int literal(4)>

Call it With Named Parameters

You can call parameters by name as well like our obj-c friends.

echo truncate(
    value = "hello there how are you?",
    length = 4,
    ellipsis = "(lol)",
)
# hell(lol)

echo truncate("Morse Code Rulez", ellipsis = "dot dot dot")
# Morse dot dot dot

echo truncate(ellipsis = "!!!", value = "Stellar stuff.")
# Stella!!!

Flexible Function Returns

We just saw how to invoke functions, but there’s also flexibility when returning values from a function too: explicit, implicit, and result.

Explicitly via return is good for bailing from an early exit.

proc guess(a, b: int): string =
    let x = 1.0
    if a == 0: return "nope"
    if b == 0: return "uh oh"
    $(a / b + x)

Also, implicitly, the last expression evaluated is assigned the return value.

Another way is to assign the reserved result variable:

proc score(a, b, c: bool, marioKartRules = true): int =
    if a: result.inc
    if b: result.inc
    if c: result.inc
    if not marioKartRules:
        return
    if result < 2:
        result.inc 10

A few things to notice here:

  1. result wasn’t defined
  2. result wasn’t initialized
  3. We returned from a branch without a value

Again with the pitchforks? Let me explain.

  1. All procedures are given the result variable for “free”
  2. result is set to the “default” value of appropriate return type – e.g. int = 0, string = "" (as of 0.19), refs are null
  3. when you exit a function, the value of result at time of the return will be its default value

Doc Tests

Doc tests allow you to tie runnable examples of your code alongside your documentation. The premise is that you’ll keep your docs up-to-date if you can’t generate them without passing the tests.

rust and elixir have this, as I’m sure other languages do. This concept is fairly new to me, though.

wfw311_crypto.nim
proc encrypt*(clearText: string): string =
    ## Pass a clear text value to encrypt a password using
    ## the Windows 3.1.1 for Workgroups algorithm.
    ##
    ## Example:
    runnableExamples:
        doAssert "password".encrypt == "********"
        doAssert "".encrypt == ""

    if cleartext.len == 0:
        ";)"
    else:
        "*".repeat(clearText.len)
$ nim c -r wfw311_crypto.nim
(compiles ok)
$ nim doc wfw311_crypto.nim
system.nim(6)            wfw311_crypto_examples1
system.nim(3790)         failedAssertImpl
system.nim(3783)         raiseAssert
system.nim(2830)         sysFatal
Error: unhandled exception: /home/steve/.cache/nim/wfw311_crypto_d/runnableExamples/wfw311_crypto_examples1.nim(6, 12) `encrypt("") == ""`  [AssertionError]

Running the doc command will fail in the 2nd assertions of the runnableExamples block. It was ";)" instead of "".

Heads up: this only works with exported functions.

By the way, did you notice that I snuck another cleartext vs clearText? No you didn’t. Liar.

Exporting

A .nim file is module and what happens inside a module stays inside a module. Your functions, constants, and variables are isolated until you tell the compiler otherwise.

That is done with the * suffix.

securitay.nim
let privateKey = "p@ssw0rd"
let publicKey* = "password"

publicKey is available from other files (e.g. from securitay import publicKey), but privateKey is not.

If you’re familiar with javascript, it’s like using export (minus the gotchas). The * identifier feels cryptic at first, but it grows on you. Now I wish I had it back in javascript.

The same technique is true for properties on custom types.

user.nim
type
    User* = object
      username*: string
      password: string

See how password is missing a *? That means, outside this file, anyone who uses the user.password will get an error. This works like the fileprivate modifier in swift.

Defer

I first saw this in go. It allows you to queue up code to execute before the local scope finishes.

bad_dba.nim
proc backupDb() =
    let db = openDb()

    # this will always run as the last thing
    # this function does
    defer:
        db.close()

    # even if you do stuff like this:
    db.lockTheUserTable()
    raise newException("party-parrot")

Admittedly, I expected to use this feature more than I do.

Iterators

Iterators are functions that return more than once. You use them in for statements.

They are defined like a proc except use the iterator keyword. Instead of returning values once, you yield values multiple times.

abba.nim
import os

type
    Member* = tuple[name: string, original: bool]

iterator aceOfBaseGroup(): Member =
    yield ("linn", true)
    yield ("jenny", true)
    sleep 500 # simulate some work
    yield ("ulf", true)
    yield ("jonas", true)
    yield ("clara", false)
    echo "i saw the sign!"

for name, original in aceOfBaseGroup():
    if not original:
        break
    echo name
$ nim c -r abba.nim
linn
jenny
(...then wait a half a sec)
ulf
jonas

In this example, we’ll never see the sign in the terminal because of the break statement.

You can use them in long running processes where you have multiple steps or need a progress bar. They’re also great for querying nested data structures, for example for p in paragraphTags(dom).

staticExec and staticRead

With staticExec you can read a string in from a command at compile time and use it at runtime. staticRead does the same, but reads from a file.

static.nim
const lines = staticExec("cat static.nim | wc -l")
const source = staticRead("static.nim")
echo source
echo lines & " lines!"
$ nim c -r static.nim
const lines = staticExec("cat static.nim | wc -l")
const source = staticRead("static.nim")
echo source
echo lines & " lines!"
3 lines!

note: wc -l is counting line breaks

This is nice-to-have since you won’t have to roll your own build tools for this purpose.

when isMainModule

The when keyword can be used for conditional compilation. For example, you can detect platforms, OSes, compiler defines/flags (like nim -d:omg) and more. Nothing new here.

I was a bit surprised to see a lot of people using this for writing tests within the same module. Maybe I’ve spent too much time in javascript land though.

lies.nim
proc add(a, b: int): int =
    a + b + 1

when isMainModule:
    # this will fail, but only if you run this file
    doAssert add(1, 1) == 2

You can make a module contain the implementation, documentation, and tests. All as a single file. That’s kinda cool, although I’m not entirely sure how I feel about it just yet.

Asynchronous IO

Like Node.JS, nim has an async io mode. You can decorate your functions with the {.async.} pragma and then await on future-based functions inside. Great for writing servers.

trebek.nim
import asyncdispatch

proc alex() {.async.} =
    echo "this"
    await sleepAsync 1000
    echo "is"
    await sleepAsync 1000
    echo "jeopardy!"

waitFor alex()
$ nim c -r trebek.nim
this
(...dramatic pause)
is
(...wait for it)
jeopardy!

Many of the libraries have async versions such as sockets, http clients & servers, and file-system.

Pragmas

Pragmas are declarative markers which attach to things like functions, properties, types, statements and more. If you’re familiar with decorators in typescript or attributes in C# then you know what these are. It’s very meta.

They are enclosed within {. and .} and multiple pragmas will be comma-separated. They can also take parameters by passing a : after the name.

Here’s a few examples of some pragmas found in the nim standard library.

  • {.emit.}
  • {.deprecated.}
  • {.noSideEffect.}
  • {.inline.}
  • {.raises.}
  • {.importc.}

And here’s an example of {.header.}, {.importc.}, and {.varargs.} working together to import a function from a c library.

proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}

There are dozens of these built-in that do everything from provide hints to the compiler, generate code, modify the linking steps, and more.

You can build your own.

The {.emit.} Pragma

The docs say “don’t do it” and “sloppy” and “highly discouraged”.

OTOH, when someone hands you a big red button labeled “Don’t Press”, you press it.

Ladies and gentlemen, I present… the golden escape hatch:

c_in_nim_to_c.nim
proc showtime() =
    let emote = "weeee!".cstring
    {.emit: """
        printf("Straight to hell I go, %s!\n", `emote`);
    """.}

showtime()
$ nim c -r c_in_nim_to_c.nim
Straight to hell I go, weeee!!

And it gets better.

Effect System

nim has ways to give the compiler more information about the expectations of your functions. If you want to disallow side-effects or ensure no exceptions can be raised, you can do this through the effect system.

For example, this won’t compile:

oops.nim
# echo writes to the terminal ... this is a side-effect
proc sayHello(name: string) =
    echo "hello " & name

# a func is a proc that can't have side effects
func hi = sayHello "steve"
$ nim c -r oops.nim
oops.nim(6, 6) Error: 'hi' can have side effects

And There’s Still More

I’ll stop here, but the list keeps going!

I didn’t talk about any of the impure libraries (ones that rely on 3rd party libraries) such as postgresql, openssl, pcre. I didn’t cover any of the parsers, threading, data structures, or string utils. if and switch statements are expressions. nim secret repls…

The official tutorial and docs have more to explore when you’re ready to dig in.

I still have the training wheels on for this language, but I’m doing c bindings now and writing lexers and parsers. Trust me. I’m not a lexer and parser kinda guy. You can probably tell. :)

I’m finding nim comfortable and approachable despite the huge feature set.

Batteries included, as they say.