Nix-build执行default.nix报pkgs未定义错误,但Nix repl可正常运行
Why does my default.nix throw "undefined variable 'pkgs'" when the variable is defined in the same attribute set?
这个问题其实是Nix里属性集和let绑定的求值规则差异搞的鬼,很容易和REPL的行为混淆,我来给你拆解清楚:
原代码的问题所在
你的初始default.nix是一个普通属性集:
{ pkgs = import <nixpkgs> {}; mkDerivation = import ./autotools.nix pkgs; hello = import ./hello.nix { inherit mkDerivation ;}; }
Nix处理普通属性集时,每个属性的定义表达式是在不包含该属性集其他属性的作用域里求值的。也就是说,当计算mkDerivation右边的代码时,当前作用域里根本没有pkgs这个变量——因为pkgs是这个属性集里的另一个属性,普通属性集不允许内部属性互相引用。
为什么REPL里能正常运行?
你在REPL里逐行输入的代码,是在全局作用域依次定义独立变量:
nix-repl> pkgs = import <nixpkgs> {} nix-repl> mkDerivation = import ./autotools.nix pkgs nix-repl> hello = import ./hello.nix { inherit mkDerivation ;}
每一行定义的变量都会被加入当前全局作用域,后面的行自然能直接引用前面定义的pkgs和mkDerivation,这和文件里属性集的求值逻辑完全不是一回事。
你的解决方法为什么有效?
你改成的let-in结构:
let pkgs = import <nixpkgs> {}; mkDerivation = import ./autotools.nix pkgs; in { hello = import ./hello.nix { inherit mkDerivation ;}; }
let块里的绑定是按顺序求值的,且后续绑定可以引用前面已经定义的绑定——因为let的作用域是嵌套的,前面的变量会被自动加入到后续绑定的作用域中,所以mkDerivation能正常拿到pkgs,最后in后面的属性集也能引用mkDerivation。
另一种可行的解决方法:递归属性集
除了let-in,你也可以用递归属性集(rec { ... })来修改原代码,同样能解决问题:
rec { pkgs = import <nixpkgs> {}; mkDerivation = import ./autotools.nix pkgs; hello = import ./hello.nix { inherit mkDerivation ;}; }
递归属性集允许内部属性互相引用,但要注意避免循环依赖(比如如果pkgs反过来引用hello就会报错)。不过一般来说,let-in的结构可读性更好,尤其是当绑定较多的时候。
关键规则总结
- 普通属性集(
{ ... }):内部属性无法引用同属性集里的其他属性,作用域不包含自身属性。 - 递归属性集(
rec { ... }):支持内部属性互相引用,但要警惕循环依赖。 - let-in结构:绑定按顺序求值,后续绑定可引用前面的绑定,是Nix组织依赖绑定的常用方式。
- REPL逐行赋值:属于交互式环境特性,每一行都在全局作用域添加变量,和文件中的求值逻辑不同。
内容的提问来源于stack exchange,提问作者KoKlA
相关产品推荐
相关产品推荐

