February 26th, 2026

Better pure nix “forEachSystem”

Short and sweet improvement on my previous post

So, turns out there is a way simple way of doing what have done on the packaging Go with nix post, the more you know…

What am I referring to?

At the very last part of my post, I introduce the idea of the forEachSystem function. This function is composed of a few steps, where I create an attribute set for each system I would like to support. Although that works and makes sense, there is a far more succinct way of doing it, without introducing any external library.

Alright… So, this is what I had:

let
  systems = [
    "x86_64-linux"
    "aarch64-linux"
    "x86_64-darwin"
    "aarch64-darwin"
  ];

  forEachSystem = (callback: 
        # Turns the list of system specific attrsets into an attrset 
        # where the attribute `name` is the system name.
        builtins.listToAttrs ( 
    # Iterates over the `systems` array, creating {systemName, attrset} pairs.
    builtins.map 
      (system:
        let
          pkgs = import nixpkgs { system = system; };
        in
        {
          name = system;
          value = callback pkgs;
        })
      systems
  )
  );
in

It is as vebose as it gets, every step of the transformation is there.

The better way?…

While working on the Kronk project nix support I stumbled on a better way of doing it. Using the genAttrs, we do pretty much exactly what I was doing combining map and listToAttrs.

let
  systems = [
    "x86_64-linux"
    "aarch64-linux"
    "x86_64-darwin"
    "aarch64-darwin"
  ];
  forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
in
{
  devShells = forAllSystems (
    system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
    in
    {
      default = pkgs.mkShell {
        buildInputs = [
          pkgs.go_1_26
        ];
      };
    }
  );
.
.
.

And that is it, short and sweet, just as I promised.