DEV Community

Cover image for Recursive/Attribute Sets of Nix Expressions
domagoj miskovic
domagoj miskovic

Posted on • Edited on

Recursive/Attribute Sets of Nix Expressions

This is going to be a beginner oriented exploration about a fundamental data structure in Nix language - Attribute sets.

Attribute sets are a collection of key-value pairs where each unique key is associated with a value. These key-value pairs or as we sometimes call them maps are also called in a more general term associative arrays, or dictionaries in Python, hashmaps in Java, objects in Javascript and so on.

The Nix manual states that attribute set is a collection of name-value-pairs (called attributes) enclosed in curly brackets ({}). Names and values are separated by an equal sign (=). Each value is an arbitrary expression terminated by a semicolon (;). Attributes can appear in any order and an attribute name may appear only once.

So this means they are unordered which goes along with Nix being lazy evaluated language. But the reason they are unordered is because they do not represent sequences but mappings of key-value-pairs. This made me think of category theory and another one of brilliant Bartosz Milewski lectures where he compared a table with cached functions.

In Nix language there are basically two kinds of these attribute sets. There are these regular non-recursive attribute sets that cannot reference any attribute within and then there are the non-regular recursive attribute sets that can access any attribute which means they can construct complex configurations and interdependent relationships between attributes. OK what does that mean? Because It took me a while to really understand the difference.

So the thing about the recursive attribute set is that one can refer to any attribute within the set.

A very small example of this would be:

rec {
  a = 1;
  b = a + 2; # b refers to a, which is defined in the same attribute set
  c = b * 3; # c refers to b, which is defined in the same attribute set
}
Enter fullscreen mode Exit fullscreen mode

So what this means is that when we are designing complex build processes we can self reference within the set and access different values. Like deriving a new attribute from an existing one:

rec {
  user = "alice";
  homeDirectory = "/home/${user}";
}
Enter fullscreen mode Exit fullscreen mode

Now this makes me think how come I can't recognize when I look at my configuration.nix file which expressions are recursive and which are not by just looking at the syntax. What I mean is that my user is declared with a set but there is no mention of a recursive set or the rec keyword. Now chatgpt answers me on this:

You are correct that recursive attribute sets are typically declared using the rec keyword. However, in NixOS and your configuration.nix file, you might not always see rec being used explicitly. This is because the NixOS configuration is built using the NixOS module system, which automatically handles the creation of recursive attribute sets when needed.

Oh cool. Then it explains a bit how that process goes.

So again what is an attribute set basically? An attribute set is a collection of key-value pairs. Like a dictionary or a phone book we have a key and a value.

{
    key = "value";
}
Enter fullscreen mode Exit fullscreen mode

But we can write any other value such as a number 42, a string of “hello”, a path to a file in a system, a boolean value of true or false, a uri value or an address of a web site to say it more plainly, a list of things such as fruits, or even what is really cool - a nested attribute set:

{
  intVal = 42;
  strVal = "hello";
  pathVal = /etc/nixos/configuration.nix;
  boolVal = true;
  uriVal = "https://example.com";
  listVal = [ "apple" "banana" "cherry" ];
  nestedAttrSet = { keyA = "valueA"; keyB = "valueB"; };
  functionVal = x: y: x * y;
}
Enter fullscreen mode Exit fullscreen mode

So a nested attribute set in Nix refers to an attribute set that is used as a value within another attribute set. But what does that mean? I asked ChatGPT to quickly create a phonebook of three contacts and show nested attribute sets. Now the same applies when making packages. You have the names of the packages but then you have a different build options and even instructions or functions with these build options. So even though a phonebook example looks pretty clear, this is just a very small example of nested attribute sets.

{
  phonebook = {
    contact1 = {
      name = "Alice";
      phoneNumber = "555-1234";
      email = "alice@example.com";
      address = {
        street = "123 Main St";
        city = "Anytown";
        state = "CA";
        zip = "12345";
      };
    };
    contact2 = {
      name = "Bob";
      phoneNumber = "555-5678";
      email = "bob@example.com";
      address = {
        street = "456 Elm St";
        city = "Sometown";
        state = "NY";
        zip = "67890";
      };
    };
    contact3 = {
      name = "Charlie";
      phoneNumber = "555-9876";
      email = "charlie@example.com";
      address = {
        street = "789 Oak St";
        city = "Othertown";
        state = "TX";
        zip = "24680";
      };
    };
  };
}
Enter fullscreen mode Exit fullscreen mode

Now what this makes me think is are all attribute sets one-dimensional or are there multi-dimensional attribute sets. In some ways recursive sets I guess mimic that behavior. But not really. Recursive sets being able to reference values within themselves during a temporal state doesn't make them multi-dimensional. This just proves I do not quite understand what would multi-dimensional configuration actually mean in this context.

But that's OK. I am enjoying this exploration of nix attribute sets. Need to explore attribute sets more and how they can be constructed. Also keeping my attention to attribute sets and slowly expanding from that seems helpful because I seem to retain more information between sessions and it is easier to get back to it even for ten minutes.

Top comments (0)