mkIf vs if / then

Hi folks,

It’s obvious to me that mkIf makes more sense when I don’t need else:

config = {
  environment.systemPackages =
    mkIf config.my.custom.opt.installvim
    [ pkgs.vim ];
}

and if / then makes more sense when I do need else:

config = {
  services.resolved.dnssec =
    if config.my.custom.opt.dnssecforced
    then "true"
    else "allow-downgrade";
}

or

config = {
  services.resolved =
    if config.my.custom.opt.resolved
    then {
      enable = true;
      llmnr = "false";
      dnssec = "allow-downgrade";
    }
    else {
      enable = false;
    }
}

But are there any other considerations? Reading between lines in other posts, it seems that mkIf is prefered when it comes to NixOS configuration. But I’m not sure why…

Thank you.

Michael

3 Likes

The implicit else branch of lib.mkIf is the least important difference between that and if-then-else construct. You could achieve the same with lib.optionalAttrs, which has the same interface:

Rather, the main difference is that mkIf produces an attribute set that is handled specially by the NixOS module system and keeps the contents available:

This will allow the module system to remain aware of the contents even when the condition is false and it can validate them.

That is also one of the reasons mkIf is preferred in NixOS modules. The other is that evaluation of the condition can be more lazy.

Just note that mkIf produces a special attribute set so the result will need to be merged with lib.mkMerge – you can no longer just use standard Nix operators:

config = {
  environment.systemPackages = [
    pkgs.ripgrep
  # ⬐ THIS IS WRONG!
  ] ++ mkIf config.my.custom.opt.installvim [
    pkgs.vim
  ];
}
8 Likes

The issue described in this section can come up in surprising ways sometimes, so it’s best to default to mkIf whenever possible (though it’d be cool if we had a mkIfElse function that took a second argument for the else case)

2 Likes
mkIfElse = p: yes: no: mkMerge [
  (mkIf p yes)
  (mkIf (!p) no)
];

I usually just use this construct inline, rather than calling a function for it.

7 Likes

I’m sure almost everyone has their own mkIfElse and mkUnless :slight_smile:

2 Likes

Time to add them to lib?

2 Likes

Is it true that both yes and no get evaluated regardless of the p condition? It seems I observe such behavior.

1 Like

Under the rules of the module system indeed both branches might get partially evaluated, as iirc the module system does the sanity check of options, values and types across all branches

1 Like