Conditionals, Lists, and Built-in Operations
Conditionals, Lists, and Built-in Operations
if in Nix is a value, not a statement. The boring default is: if c then a else b (else required), lists with ++, and builtins.map / filter / length / elem.
Mental model
if isProd then 5 else 1No if without else. No if that “does” something. Both branches are expressions; the unused one stays lazy.
Lists:
[ 1 "two" ./three (4 + 5) ]Elements are space-separated. [ 1 2 ] ++ [ 3 ] is [ 1 2 3 ]. Indexing is builtins.elemAt xs 0 (0-based). There is no xs[0] in the language.
lib.optional cond x is if cond then [ x ] else [ ] — the boring way to maybe-append one item.
Worked examples
Case 1: Flags
Save as conditional_config.nix:
# conditional_config.nix
let
isProduction = true;
in
{
replicas = if isProduction then 5 else 1;
logging = if isProduction then "warn" else "debug";
}nix eval --file conditional_config.nix --jsonOutput:
{"logging":"warn","replicas":5}
Flip isProduction to false and eval again.
Case 2: map / filter
Save as list_operations.nix:
# list_operations.nix
let
tableNumbers = [ 1 2 3 4 5 6 7 8 9 10 ];
isPriority = n: n <= 3;
priorityTables = builtins.filter isPriority tableNumbers;
deskWorkers = builtins.map (n: { table = n; status = "active"; }) priorityTables;
in
{
inherit deskWorkers;
n = builtins.length priorityTables;
hasTen = builtins.elem 10 tableNumbers;
}nix eval --file list_operations.nix --jsonOutput:
{"deskWorkers":[{"status":"active","table":1},{"status":"active","table":2},{"status":"active","table":3}],"hasTen":true,"n":3}
Case 3: else is required
Save as bad_if.nix:
# bad_if.nix
if true then 1nix eval --file bad_if.nixOutput (shape):
error: syntax error, unexpected end of file, expecting 'else'
To omit an attr, do not skip else. Use:
{ } // (if enable then { workers = 4; } else { })or lib.optionalAttrs enable { workers = 4; }.
Case 4: Concatenate packages-style lists
Save as packages.nix:
# packages.nix
let
common = [ "git" "jq" ];
extra = [ "ripgrep" ];
in
common ++ extra ++ (if true then [ "htop" ] else [ ])nix eval --file packages.nix --jsonOutput:
["git","jq","ripgrep","htop"]
This is how environment.systemPackages concatenates across modules (the module system merges lists). Here you do it by hand.
Case 5: Lazy unused branch
Save as lazy_if.nix:
# lazy_if.nix
if true then "ok" else throw "no"nix eval --file lazy_if.nixOutput:
"ok"
The throw is not forced. Invert the condition to see it.
The trap
The trap is if cond then x; with no else, copied from Bash. Parse error.
The other trap is [ 1, 2 ] with commas. Nix lists are spaces. Commas are attrset / function-arg style. [ 1, 2 ] is often a parse error or a surprising function call.
The boring rule
ifalways haselse. Both sides are values.- Spaces in lists.
++to join. map/filter/length/elemfrombuiltins.optional/optionalAttrs(lib) for maybe-one-item.- Unused
else throwstays quiet — good for flags, bad if you meant to test both.
Try this
builtins.lengthalready in Case 2; addbuiltins.elem 3 priorityTables.- Write
[ 1, 2 ]and read the error (or the odd parse). - Case 5 with
false. lib.optional— after you havepkgs.libin the imports chapter, replace theif true then [ "htop" ] else [ ]pattern.