nomisiv.com / blog / nix-good-and-bad

Nix, the Good and the Bad

If you know me in person, you’ve probably heard me rant about nix serveral times. I have mixed feelings about nix, which I will try to express in this blog post. But before any of that, what is nix, and what is it good for?

I could go on for way too long describing everything that nix does in too great detail for the purposes of this blog post. In fact, I tried writing a comprehensive guide of everything that nix is, and what it isn’t. It was supposed to be a multi-page thing, with it’s own dedicated section on this website, but ultimately the momentum and my motivation ran out and I abandoned the ambitious project.

like too many other projects of mine…

What is nix?

Nix is commonly separated into three different parts, successively building upon each other:

Nix - the language and build system

At it’s core, nix is essentially just a build system. The user can write derivations (build scripts written in the Nix programming language), which can be evaluated (built) into store paths (a package living inside the /nix/store/). By evaluating derivations in a sandbox without access to the rest of the filesystem or the internet, Nix makes it very difficult to accidentally make non-reproducible derivations. The idea here is that a derivation build on one machine should be completely identical to the same package built on another machine, even if it has a different architecture or different OS.

Nixpkgs - the package repository

Built upon Nix - the language, is Nixpkgs - the package repository. It consists of more than 80.000 packages, which actually is the largest repository of all linux distibutions (according to repology).

You can see if Nixpkgs has your favorite package here!

NixOS - the declarative operating system

In NixOS, instead of editing the state of your machine configuration by imperatively executing comamnds in your terminal, you have a configuration.nix-file (or a flake.nix, if you’re a cool nix kid) which explicitly declares the desired state of your machine. This includes things like which programs should be installed and how they should be configured. After editing your NixOS configuration to your hearts content, you then run nixos-rebuild switch to activate that updated configuration.

The good

Now that you’ve seen the tip of the iceberg, let’s get into my opinions.

Declarativity

Editing a file and running a command to update system state might seem like an extra step and a hurdle to some, but in my experience, it’s 100% worth it. Because of all this explicit state collected in one location it becomes trivial to answer questions like “how did I configure that program?”, or “how did I work around that bug?”. It is all documented right there. No more digging around in the filesystem trying to find a bug in that one systemd-unit that you hand wrote for a package you build from source.

Another benefit of this in conjunction with NixOS’ module system is that it’s possible to write one single configuration for all your computers to share, disabling the parts you don’t need for that particular machine.

NixOS generations

Every time you nixos-rebuild switch your system, another generation gets added to your list of boot entires. So if your new generation is borked in some way you can just nixos-rebuild switch --rollback, or reboot back into a known good version. This can really save your ass if you for example upgrade to a new kernel, but discover it’s not working as it should.

nix shell

Ever wanted to try out a program without installing it on your computer forever? Just run nix shell nixpkgs#<program> and nix will fetch the program for you and put you in a shell with the program available. When you leave the shell it will no longer be available (but it will still be cached in your /nix/store)

Direnv + flakes

Using flakes you can neatly tie build instructions, testing, and nixos modules, but also development shells with all your development dependencies automatically installed. Additionally this can be further improved by using direnv to automatically enter your development shells when you enter the project folder. Contributing to a project with a flake.nix in the root make me confident I will have no issues setting up the development environment for the contribution I’m making.

The mixed

Some aspects of nix are like a douple edged sword, useful for slicing your problems, but also useful for slicing yourself.

nixpkgs GitHub repo

The nixpkgs GitHub repo is both a benefit and a drawback of the Nix ecosystem. For one, it allows super easy access to the actual source of the distribution. How is this package implemented? You can look it up! It also makes it super easy to contribute new packages or modules to nixpkgs, as it’s as simple making a PR. However, maybe it’s because so easy to contribute new packages, or maybe it’s because nixpkgs doesn’t have enough maintainers, but there are so many issues and PRs that GitHub literally won’t count them all (+5k). This makes having anyone actually review and merge your package very difficult.

Documentation

Documentation is also a bit of a mixed bag. There are a multitude of sources: nixpkgs manual, NixOS manual, NixOS wiki, Nix Pills, Zero to Nix. (These are not even all of them) The first two manuals are single pages, making them painfully slow to load, even on modern hardware. The documentation is often good, but sometimes it’s lacking or incomplete. Unfortunately this makes Nix needlessly difficult to pick up.

The bad

Lastly, we have the absolute worst parts of nix; the things I’m referring to when I say that nix sucks.

Build times

Nix is a rather difficult language to benchmark. It’s not like a traditional programming language, and it’s not meant to do the same things as one. So there isn’t really anything to compare against, apart from guix, which I haven’t tried. But even though it is in a class of it’s own, I feel like Nix is needlessly slow. The evaluation time obviously goes up as with the complexity of your NixOS configurations, but when evaluating all the systems I have declared in my flake can take up to 10 minutes before nix is done “thinking” and starts to actually build stuff. Even when evaluating only one system, it’s more than a handful of seconds before anything really starts to happen, which really kills the development cycle.

Error messages

Nix error messages are horrible. I have not seen another language with less helpful error messages. If you’re lucky, and keep to the beaten path, you will most likely have alright error messages saying what’s wrong and what option is wrong. It doesn’t tell you where though. If you are unlucky, or want to actually write functions and anything more complex than the bare minimum, you will run into stack traces filling up half your terminal buffer, filled with references to internal functions in nixpkgs that you didn’t write, with incredibly cryptic descriptions. If you actually scroll through this stack trace, you might find a reference to a file that you actually have created yourself, but that’s not a guarantee. More often than not, I find myself using trial and error to work around the errors. Sometimes you’ll run into infinite recursions for inexplicable reasons. My preferred method of resolving those is rewriting the entire code in another way to see if it fixes it. And while debugging any errors, the 10+ second build times certainly aren’t helpful.

Dynamically linked libraries

Due to how nix wants complete control over the entire dependency chain (which, again, has it’s benefits) it makes it really difficult to gradually approach nix. It’s more of an “all or nothing”-kind of thing. You can say goodbye to downloading precompiled binaries outside of nix itself, as more often than not they depend on dynamically linked binaries. The issue with this is that because of how nix is designed, the libraries aren’t in the usual places, and if the binary isn’t specifically linked to the right path, it’s just going to crash. There are ways to work around this with tools like nix-alien, or nixGL, but it’s cumbersome and not idiot proof. So if it ain’t from the nix store, you probably ain’t runnin’ it, man.

The conclusion

Unfortunately there are essentially no alternatives to Nix. The closest project would be guix, but as far as I’m aware it lacks many of the features nix has, apart from being a libre-only distro. I mean, I like the idea of libre, but I also like my steam games. In another way, docker could also be an alternative, but in my experience docker manages to be even worse, and again only share some aspects of nix.

Shortly said, this is how I like to put it:

Nix is pretty bad, but it’s the best that there is.

Thanks for actually reading all this lol