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" ]
2 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