Nix lang: short syntax to add element to a list

In my configuration.nix I have this line:

users.users.me.extraGroups = users.users.me.extraGroups ++ [ "docker" ];

Is there a shorter syntax than this? I was hoping that something like mylist ++= [ "foo" ] would work.

Oh actually I just did a nixos-rebuild switch and the line above doesnā€™t even workā€¦

error: attribute 'users.users.me.extraGroups' at /etc/nixos/configuration.nix:122:3 already defined at /etc/nixos/configuration.nix:105:

You can just do:

users.users.me.extraGroups = ["docker"];

I havenā€™t checked but lists merge semantic is generally to append between modules.

4 Likes

Something to remember here is that Nix is declarative, not imperative. You donā€™t modify variables, you simply define them.

That said, the error you gave suggests that this particular attribute was already defined earlier in the same file.

3 Likes

++= is the default operation for lists in nixos configuration (module system), however we canā€™t say it ā€œappends to endā€ (it depends on module order):

āžœ nix repl '<nixpkgs/lib>'
Welcome to Nix version 2.3pre6895_84de821. Type :? for help.

Loading '<nixpkgs/lib>'...
Added 361 variables.

nix-repl> m1 = { options.foo = mkOption{}; }

nix-repl> m2 = { foo = [ "hello" ]; }

nix-repl> m3 = { foo = [ "world" ]; }

nix-repl> (evalModules { modules = [ m1 m2 m3 ]; }).config.foo
[ "world" "hello" ]

nix-repl> (evalModules { modules = [ m1 m3 m2 ]; }).config.foo 
[ "hello" "world" ]

To control order of elements in a list you can use lib.mkBefore (order 500), lib.mkAfter (order 1500), default = (order 1000) and mkOrder (custom order):

nix-repl> m4 = { foo = mkBefore [ "hello" ]; }

nix-repl> m5 = { foo = mkAfter [ "world" ]; }

nix-repl> (evalModules { modules = [ m1 m4 m5 ]; }).config.foo 
[ "hello" "world" ]

nix-repl> (evalModules { modules = [ m1 m5 m4 ]; }).config.foo 
[ "hello" "world" ]
3 Likes

Just to be full here: the error you see means you have users.users.me.extraGroups defined twice in same file. Or maybe you have something like:

{
   users.users.me = {
      ...
      extraGroups = [ ... ];
   };
   users.users.me.extraGroups = ...;

It will still think this is duplicate definition (attr keys must be unique in a single attrset).

3 Likes

Yeah thatā€™s the problem. I initially want to mutate the users.me.extraGroups list but now I understand thatā€™s not possible.

What about something like:

let
  firstGroups = [ "foo" ];
  secondGroups = [ "bar" ];

in
{
  users.users.me.extraGroups = [ "docker" ]
    ++ firstGroups ++ secondGroups;
}

If your defining users.me.extraGroups in multiple files they will be merged automatically when deploying the system. For example:

file1.nix

users.me.extraGroups = [ "foo" ];

file2.nix

users.me.extraGroups = [ "bar" ];

end result after building configurations:

users.me.extraGroups = [ "foo" "bar" ];
# or possibly [ "bar" "foo" ] as evaluation order is non-deterministic iirc

Thereā€™s this syntax trap that might confuse people. When I was doing a Nix talk (sorry no recording) and I showed them this slide, someone asked: How is that purely functional? The code in question is here for convenience:

{
  a = 1; 
  b = {
    x = 0;
  };
}

Turns out at this point in time, when people see {} and a = 1;, they automatically think itā€™s a block with an assignment expression. Itā€™s not! This is actually a complex literal equivalent to this JSON:

{
  "a": 1,
  "b": {
    "x": 0
  }
}

And you can abbreviate it to:

{
  a = 1; 
  b.x = 0;
}

If you say b.x = 0; and b.y = 1;, they will ā€˜mergeā€™ into a single b, but you canā€™t say b.x twice just like you canā€™t say a twice. NixOS modules merging the configurations is a completely different mechanism at play.

nix repl is a good place to play with the syntax of the language