"The heart of Nix configuration: finding the right merge point in a complex hierarchy."
Recursive Attribute Sets: rec { ... }#
An ordinary attribute set does not add its own attributes to the lexical scope. To reference attributes by name from within the set, use rec or define shared bindings in a let expression first. If an outer variable has the same name, an ordinary attribute set references that outer variable.
Incorrect:
{
a = 1;
b = a + 1; # 报错:变量 a 找不到
}Correct:
rec {
a = 1;
b = a + 1; # 结果为 b = 2
}How Attribute Merging Really Works#
1. Using // (Merge)
This is merging at the Nix language level.
let
defaults = { user = "guest"; shell = "/bin/sh"; };
overrides = { shell = "/bin/bash"; };
in defaults // overrides
# 结果:{ user = "guest"; shell = "/bin/bash"; }- Note:
//performs a shallow merge. If both sets contain nested sets with the same name, the latter replaces the former outright.
2. Using lib.mkMerge (NixOS Modules Only)
In NixOS configuration files, we rarely use // directly. The NixOS module system merges configuration for us:
{
environment.systemPackages = [ pkgs.git ];
}
# 另一个模块中:
{
environment.systemPackages = [ pkgs.vim ];
}
# 结果:[ pkgs.git pkgs.vim ] (列表选项默认按模块规则合并;顺序可用 mkBefore/mkAfter 调整)Dotted Paths#
Use dotted paths to define deeply nested attributes directly and reduce nesting.
Long form:
{
services = {
openssh = {
enable = true;
};
};
}Concise form:
{
services.openssh.enable = true;
}Practical Example: Dynamic Attribute Names#
Use ${...} to generate attribute names dynamically.
let
username = "alice";
in {
users.users."${username}" = {
isNormalUser = true;
};
}References & further reading
Adapted from technical notes. Examples use generic names.