Understanding top level arguments in configuration.nix

i never understand what arguments to put in the configuration.nix’ function arguments.

  1. If i use pkgs or lib, i know i have to call them, but I think there’s more to it?

  2. I would expect config to be called when I edit the top level attribute config, but clearly, this isn’t the case.
    Same for options but I haven’t worked with options yet.

  3. ... I am quite clueless because I only know it like this :

{ foo, ... } @ args: foo + args.bar

i.e: why is this working?

{...}: {programs.git.enable = true ;} ;}

The best explanation I could find was this, from the NixOS manual.

1 Like

Why would you expect that? When you do { ... }: { foo = "bar"; }, you aren’t using any config variable; you’re returning an attrset with a key named foo. The module system itself takes the values returned by all modules and aggregates them into the config argument that they are all passed (which is possible because of lazy evaluation).

1 Like

You’re adding them to the list of function args; you’re not calling them, they’re not functions.

You’re just using no module args, i.e. ignoring all the args passed to the function.
I suggest reading the nix manual regarding function syntax: Syntax and semantics - Nix 2.30.3 Reference Manual

However, It’s kind of silly to write out {...}: IMO (in the context of the module system), so I would just write

{
  programs.git.enable = true;
}

because modules can either be attrsets or functions that return an attrset.

2 Likes

Nix functions declaration:

ie. declaration ie. call
f = a: a + 1 takes one numerical arg a f 42
f = a: a + "1" takes one string arg a f "42"
f = a: a ++ [1] takes one list arg a f [42 43]
f = a: a.b + 1 takes one attr set a, with b attr f { b = 42; }
f = { b }: b + 1 takes one attr set a, with b attr f { b = 42; }
but fails with unknown attrs f { b = 42; c = 43; }
f = { b, ...}: b + 1 ignores unknown attrs f { b = 42; c = 43; }
f = { b, ...}@a: b + 1 + a.c put all attrs in a f { b = 42; c = 43; d = 44; }
f = { ... }: 1 + 1 ignore all attrs, but has to be attr set f { b = 42; }

note: nix functions always takes one argument only, but this one argument can be an attr set with N attrs. Alternatives to expecting more arguments are expecting a list and currying (a function that returnas a function like: a: b: a +b).

Nix Modules Declaration:
Nix modules is a framework, used by NixOS, that not only imports your config files, but does other funkier things likes validation (ie an config has to be declared as option before it is used), merge, etc.

Modules examples:

  • Attr set only
{ # no args at all
  imports = [ ./otherModule.nix ];
  programs.firefox.enable = true;
}
  • Function
ARGS: { # ses previous table
  imports = [ ./otherModule.nix ];
  myConfig = true; # only fails because myConfig isn't defined
}
  • Function with options (when defining your own options)
{lib, config, ...}: {
  imports = [ ./otherModule.nix ];
  options.myConfig = lib.options.mkEnableOption "MyConfig"; # see also mkOption
  # now the previous example works

  # This is the same as `programs.firefox.enable` of first example, 
  # but since this files has `options`, it requires `config` prefix to set configs
  config.programs.firefox.enable = config.myConfig;
  # ^-- this `config` and this other --^, have same name are different,
  # first one is what we set, 
  # the second one is what some one, some else will set and came from arguments
}

What arguments:
Most commons are pkgs, lib and config, but docs, say there is also options

We can define new arguments where Nix Modules framework is used (NixOS, Home-Manager, DevShell, DevEnv, etc):

nixpkgs.lib.nixosSystem {
  modules = [ ./configuration.nix ];
  system  = "x86_64-linux";
  specialArgs = { inputs = inputs; };
};

Now all my nix modules has an attr in argument inputs; while is common for flake users to do the same, modules expecting inputs in argument will only work for them, affecting reusability of your modules with other non flake users or even flake users who doesn’t set this argument.

There is another way to add arguments to modules, by setting it in any module.

{
   _module.args.myArg = 2;
}

Note that arguments defined this way cannot be used for imports

{ inputs, myArg, ...}:
{
  imports = [
    ./${inputs.someFlakeDep}/otherModule.nix # may work if planets are aligned
    ./${myArg}/otherModule.nix # fail for sure
  ];
}
4 Likes

Really specific answers aside, you’re basically just asking how the module system works.

The module system tutorial explains modules quite well. That combined with the nix language tutorial should give you a good understanding of what those function args mean.

The important thing to realize is that nix modules are just normal nix files, so the language tutorial fully applies. The module system tutorial explains how NixOS uses the code you write, which makes it feel a bit different than function declarations/attrsets, but it’s ultimately the same thing.

Your use of the word calling anyway makes it sound like you’re fundamentally confused about the nix language, which is pretty common for folks encountering it as their first functional programming language. I’d really suggest taking the time to go through those tutorials and experimenting with the language a bit without the module system further obfuscating things.

3 Likes