Nix 语言特性概览
Nix 语言是为描述包构建而设计的领域特定语言(DSL),具有以下特点:
纯函数式
没有可变状态,没有副作用,函数是一等公民。相同的表达式永远求值为相同的结果。
惰性求值
表达式只在需要时才被计算。引用了 Nixpkgs 中 80000+ 个包的表达式,你只请求其中一个时,其他包的 derivation 不会被计算。
动态类型
变量类型在运行时确定,无需类型声明。但类型错误会在求值时报错,而非编译期。
不是通用语言
Nix 语言没有 I/O 操作(无法读写文件、无法执行命令),专为描述构建计划设计。实际的构建步骤在 bash 脚本中执行。
基本类型
# 使用 nix repl 交互式学习:运行 nix repl 进入
# 字符串
"hello world" # 普通字符串
''
多行字符串
开头的缩进会被自动去除
''
let name = "Nix"; in "Hello, ${name}!" # 字符串插值:Hello, Nix!
"path: ${/etc/hosts}" # 路径插值
# 数字
42 # 整数
3.14 # 浮点数
1 + 2 # 运算:3
10 / 2 # 注意:整数除法,结果为 5
# 布尔值
true
false
true && false # false
true || false # true
!true # false
# null
null
# 路径(不加引号的路径,Nix 会自动解析)
/etc/nixos # 绝对路径
./relative/path # 相对路径(相对于当前 .nix 文件)
<nixpkgs> # 尖括号路径,从 NIX_PATH 中查找
数据结构
Attribute Set(属性集)
Attrset 是 Nix 最核心的数据结构,类似其他语言的 Map/Dictionary:
# 基本 attrset
{ name = "Alice"; age = 30; }
# 访问属性
let person = { name = "Alice"; age = 30; }; in
person.name # "Alice"
# 带默认值的属性访问(or 关键字)
person.email or "unknown" # "unknown"(email 属性不存在时)
# 嵌套 attrset
{
services.nginx.enable = true; # 等价于下面的写法
}
# 等价于:
{
services = {
nginx = {
enable = true;
};
};
}
# 递归 attrset(rec):属性可以引用同一集合中的其他属性
rec {
x = 10;
y = x * 2; # y = 20,可以引用 x
}
# 合并两个 attrset(// 运算符,右侧优先)
{ a = 1; b = 2; } // { b = 99; c = 3; }
# 结果:{ a = 1; b = 99; c = 3; }
List(列表)
# 列表(用空格分隔,不是逗号!)
[ 1 2 3 ]
[ "a" "b" "c" ]
[ true false null ]
# 列表连接
[ 1 2 ] ++ [ 3 4 ] # [ 1 2 3 4 ]
# 列表索引(从 0 开始)
builtins.elemAt [ "a" "b" "c" ] 1 # "b"
# 常用 builtins 列表操作
builtins.length [ 1 2 3 ] # 3
builtins.head [ 1 2 3 ] # 1
builtins.tail [ 1 2 3 ] # [ 2 3 ]
builtins.map (x: x * 2) [ 1 2 3 ] # [ 2 4 6 ]
builtins.filter (x: x > 2) [ 1 2 3 4 ] # [ 3 4 ]
函数
Nix 的函数语法简洁,所有函数都是单参数的(多参数通过柯里化实现):
# 最简单的函数:参数: 函数体
x: x + 1 # 一个将输入加 1 的匿名函数
# 调用函数(函数 空格 参数,不是圆括号!)
(x: x + 1) 5 # 6
# 用 let 绑定函数名
let
double = x: x * 2;
add = x: y: x + y; # 多参数:柯里化(返回另一个函数)
in
double 21 # 42
add 3 4 # 7,等价于 (add 3) 4
# 解构 attrset 参数(Nix 最常用的函数形式)
{ a, b }: a + b # 接受包含 a 和 b 的 attrset
{ a, b ? 0 }: a + b # b 有默认值 0
{ a, ... }: a # ... 允许额外属性存在(常见于模块系统)
# 实际调用解构函数
(let f = { name, age ? 0 }: "${name} is ${toString age}"; in
f { name = "Alice"; age = 30; })
# "Alice is 30"
let…in、with 与 import
# let...in:局部变量绑定
let
x = 10;
y = 20;
sum = a: b: a + b;
in
sum x y # 30
# with:将 attrset 的所有属性引入作用域
with { a = 1; b = 2; }; a + b # 3
# 实际用途:with pkgs; 避免重复写 pkgs.
with pkgs; [ git vim curl ]
# 等价于:
[ pkgs.git pkgs.vim pkgs.curl ]
# import:导入另一个 .nix 文件(返回该文件的值)
import ./other.nix # 导入同目录下的 other.nix
import <nixpkgs> {} # 导入 nixpkgs 并传入空配置
# if…then…else(表达式,不是语句)
if true then "yes" else "no" # "yes"
let x = 5; in if x > 3 then "big" else "small" # "big"
# assert:断言(失败时终止求值并报错)
assert 1 + 1 == 2; "ok" # "ok"
builtins:内置函数
# 类型转换
builtins.toString 42 # "42"
builtins.toJSON { a = 1; } # '{"a":1}'
builtins.fromJSON '{"a":1}' # { a = 1; }
builtins.typeOf 42 # "int"
builtins.typeOf "hello" # "string"
builtins.typeOf [] # "list"
builtins.typeOf {} # "set"
# 字符串操作
builtins.stringLength "hello" # 5
builtins.substring 0 3 "hello" # "hel"
builtins.replaceStrings ["a"] ["b"] "cat" # "cbt"
builtins.match "(.*)\\.nix" "hello.nix" # [ "hello" ]
# attrset 操作
builtins.attrNames { b = 2; a = 1; } # [ "a" "b" ](排序)
builtins.hasAttr "x" { x = 1; } # true
builtins.getAttr "x" { x = 42; } # 42
builtins.removeAttrs { a=1; b=2; } ["b"] # { a = 1; }
# 文件获取(构建时用)
builtins.fetchurl {
url = "https://example.com/file.tar.gz";
sha256 = "abc123..."; # 必须提供哈希,保证可复现
}
# 从 GitHub 获取源码
builtins.fetchGit {
url = "https://github.com/user/repo";
rev = "abc123def456"; # 锁定具体 commit
}
写第一个 Derivation
Derivation 是 Nix 构建包的核心原语,用 derivation{}(低级)或 stdenv.mkDerivation{}(高级)创建:
# 使用高级 API:stdenv.mkDerivation
# 文件:hello-custom.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation {
pname = "hello-custom"; # 包名
version = "1.0.0";
# 源码来源
src = pkgs.fetchurl {
url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
sha256 = "sha256-jZkUKv2SV28wsM18tCqNxoCZmLxdYH2Idh9RLibH2yQ=";
};
# 构建依赖(build 阶段用)
nativeBuildInputs = [ pkgs.autoconf pkgs.automake ];
# 运行时依赖(产物包含的库)
buildInputs = [];
# 自定义构建步骤(默认:configure + make + make install)
buildPhase = ''
make
'';
installPhase = ''
mkdir -p $out/bin
cp hello $out/bin/
'';
# 元数据(可选)
meta = with pkgs.lib; {
description = "A program that produces a familiar, friendly greeting";
license = licenses.gpl3Plus;
platforms = platforms.all;
};
}
# 构建这个 derivation
nix-build hello-custom.nix
# 输出:/nix/store/abc123xyz-hello-custom-1.0.0
# 运行构建结果
./result/bin/hello-custom
# 输出:Hello, world!
# 查看构建产物在 store 中的路径
ls -la result # result 是指向 /nix/store/... 的符号链接
nix repl 调试技巧
# 进入交互式 Nix REPL
nix repl
# 在 REPL 中:
nix-repl> 1 + 2 # 3
nix-repl> let x = 5; in x * 2 # 10
nix-repl> :l <nixpkgs> # 加载 nixpkgs(需要几秒)
nix-repl> pkgs.hello # 查看 hello 包的 derivation
nix-repl> pkgs.hello.version # "2.12.1"
nix-repl> :t pkgs.hello # 显示类型
nix-repl> :q # 退出
本章小结
Nix 语言的语法比较小巧:掌握 attrset({})、list([])、函数(x: ...)、解构函数({ a, b }: ...)、let...in、with、import 这几个构造,就足以读懂 99% 的 Nix 配置。下一章开始将这些知识用于实际的开发环境配置。