Why Nim?

I was asked to put together a document on why I recommend the Nim language, so here follows at brief overview of my problems with the language, what I like, and some code examples. I may update this without notice.

Disadvantages

Dependency management

Nim packages can be published using the Nimble package manager. My issues with Nimble are straightforward:

I have made some effort at managing Nimble using Nix, but it's always been too fragile to work consistenly.

https://github.com/nix-community/flake-nimble

Language features

The language has many keywords, compiler pragmas, and a flexible grammer. This can be overwhelming when writting code and complicates code comprehension. Most of the features can be ignored without safety or perfomance costs, so it shouldn't be a problem for a disciplined developer.

Advantages

Compiles to C and C++

I started using Nim because I was maintaining a parts of large C++ codebase with a C++ API, but I also recognize that C++ is a deeply flawed language. Compiling to C++ allowed me to easily wrap and use these C++ ABIs. Memory management is concern when passing pointers across the threshold, but so far it hasn't been a hassle.

Wrapping C:

Opusenc wrapper

Wrapping the Genode C++ API:

Genode Terminal wrapper

Portable language runtime

The language runtime is fairly easy to port to modern (post-1970s) environments. The garbage collector is freestanding and easy to interface to native page allocators. Freestanding thread-local storage emulation is also available. Bare-metal is supported, but I haven't tried it.

Robust type system

The type system makes expressing array types, type templates, and distinct types easy. The language allows values to be passed around easily regardless if they are heap or stack values, with good mutability tracking.

An example of generating fixed array types along with procedure for converting to and from string encodings:

Tox wrapper

Not object oriented

The object types are easy to understand. Object inheritance is only applicable to object members, and only one level deep. There are no object "methods", access to object memembers can be restricted to the local module or allowed globally. Procedures can be added and overriden for objects at arbitrary locations. Due to the common influence of Oberon the objects are similar to those of Go. Object variants, also known as tagged unions, are one of my favorite features, and can be used in place of C++ inheritance or Go interface types.

An example of modeling multiple CBOR item types using a common object, the "kind" field desciminates which fields are in use for an object instance:

CBOR

Side effect tracking

The language distinguishes between procudures and functions. Functions are a subset of producers where side effects are restricted, no I/O or other changes to global state may be made. This is helpful for implementing functional datastructures or deterministic procedures.

Deterministic cryptographic signing, the library has no side-effects and relies on an entropy gathering callback:

SPHINCS⁺

Proxied content from gemini://gemini.spam.works/users/emery/why-nim.gmi

Gemini request details:

Original URL
gemini://gemini.spam.works/users/emery/why-nim.gmi
Status code
Success
Meta
text/gemini
Proxied by
kineto

Be advised that no attempt was made to verify the remote SSL certificate.