nix-playgraound/notes/002-nix-primer.md

206 lines
7.1 KiB
Markdown
Raw Normal View History

2026-04-15 11:49:36 +02:00
# Nix Primer
2026-04-15 15:47:10 +02:00
Nix is two things bundled:
2026-04-15 11:49:36 +02:00
2026-04-15 15:47:10 +02:00
1. A pure, lazy functional language for describing builds.
2. A package manager that takes those descriptions and produces content-addressed store paths.
2026-04-15 11:49:36 +02:00
Confusingly, both are called "Nix." Separate them in your head and everything gets easier.
---
2026-04-15 13:44:59 +02:00
## 1. The Mental Model: Eval, Then Build
2026-04-15 11:49:36 +02:00
2026-04-15 13:44:59 +02:00
Every `nix build` or `nix develop` runs in two phases:
2026-04-15 11:49:36 +02:00
```
.nix source ──(evaluate)──▶ .drv file ──(realise)──▶ /nix/store/...-output
│ │ │
└── Nix language └── Recipe └── Actual files
(pure, lazy) (on disk) (content-addressed)
```
2026-04-15 15:47:10 +02:00
- Evaluation runs the Nix language. Pure, in-memory, fast. Produces `.drv` files.
- Realisation actually builds things. Sandboxed. Slow. Outputs go to `/nix/store`.
2026-04-15 11:49:36 +02:00
2026-04-15 13:44:59 +02:00
You can evaluate without building (`nix eval`, `nix-instantiate`) and you can build without touching the language if you already have a `.drv`. Most errors you'll see are *eval* errors: typos in attrsets, missing arguments, and undefined names.
2026-04-15 11:49:36 +02:00
---
2026-04-15 13:44:59 +02:00
## 2. The Language in 5 Minutes
2026-04-15 11:49:36 +02:00
2026-04-15 13:44:59 +02:00
### Primitives and Collections
2026-04-15 11:49:36 +02:00
```nix
42 # integer
3.14 # float
"hello ${name}" # string with interpolation
''multi-line # indented string (strips common leading whitespace)
string''
true false null
[ 1 2 3 ] # list (space-separated, NOT comma)
{ a = 1; b = "two"; } # attrset
2026-04-15 13:44:59 +02:00
/foo/bar # path (literal, not a string!)
2026-04-15 11:49:36 +02:00
./relative/path # path relative to current file
```
Paths are their own type. When used, they're copied into the store and replaced with the store path.
### Functions
Single-argument, curried:
```nix
add = x: y: x + y;
add 2 3 # => 5
```
Attrset destructuring (how "named args" work):
```nix
mkUser = { name, age ? 0, ... }: { inherit name age; };
mkUser { name = "ada"; } # => { name = "ada"; age = 0; }
```
The `...` means "ignore extra attrs." Without it, passing extras is an error.
### Bindings
```nix
let
x = 1;
2026-04-15 13:44:59 +02:00
y = x + 1; # can reference earlier bindings (and each other: lazy)
2026-04-15 11:49:36 +02:00
in x + y
```
```nix
{ inherit x y; } # ≡ { x = x; y = y; }
{ inherit (pkgs) hello jq; } # pulls hello/jq from pkgs into this attrset
```
2026-04-15 13:44:59 +02:00
### `with` (Use Cautiously)
2026-04-15 11:49:36 +02:00
```nix
with pkgs; [ hello jq ripgrep ]
# ≡
[ pkgs.hello pkgs.jq pkgs.ripgrep ]
```
2026-04-15 13:44:59 +02:00
Convenient in package lists. Avoid at the top of a file: it silently shadows names and makes debugging hard.
2026-04-15 11:49:36 +02:00
### Laziness
2026-04-15 13:44:59 +02:00
Nothing is evaluated until needed. This is why you can have large attrsets like `nixpkgs` (100k+ packages) and only pay for what you use.
2026-04-15 11:49:36 +02:00
```nix
let broken = throw "nope"; ok = 1; in ok # => 1, never evaluates `broken`
```
### `import`
```nix
import ./foo.nix # evaluate foo.nix, return its value
import ./foo.nix { x = 1; } # if foo.nix is a function, also call it
```
---
2026-04-15 13:44:59 +02:00
## 3. Derivations: The Core Primitive
2026-04-15 11:49:36 +02:00
2026-04-15 15:47:10 +02:00
A derivation is "a build, described as data." You create one with `derivation { … }` (low-level) or `pkgs.stdenv.mkDerivation { … }` (the nixpkgs wrapper you'll actually use).
2026-04-15 11:49:36 +02:00
Minimal example:
```nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation {
pname = "hello-note";
version = "0.1";
2026-04-15 13:44:59 +02:00
src = ./.; # path: gets copied into the store
2026-04-15 11:49:36 +02:00
installPhase = ''
mkdir -p $out/bin
echo '#!/bin/sh' > $out/bin/hello-note
echo 'echo hello from nix' >> $out/bin/hello-note
chmod +x $out/bin/hello-note
'';
}
```
Key ideas:
2026-04-15 15:47:10 +02:00
- `$out` is the output store path the build writes into.
- Phases (`unpackPhase`, `buildPhase`, `installPhase`, …) are shell snippets `stdenv` runs in order. Override the ones you need.
- No network in the build sandbox, except for fixed-output derivations (whose hash you declare up front).
- Every input (source, compiler, env vars) becomes part of the hash. Change anything and you get a fresh store path.
2026-04-15 11:49:36 +02:00
---
2026-04-15 13:44:59 +02:00
## 4. Store Model: Why This Is All Different
2026-04-15 11:49:36 +02:00
2026-04-15 13:44:59 +02:00
Traditional package managers put files in `/usr/bin`, `/usr/lib`, and similar shared locations. One version at a time. Upgrades mutate shared state.
2026-04-15 11:49:36 +02:00
2026-04-15 15:47:10 +02:00
Nix puts every build in `/nix/store/<hash>-<name>/` with the hash derived from all its inputs (recursively). That gives you:
2026-04-15 11:49:36 +02:00
2026-04-15 15:47:10 +02:00
- Atomic upgrades and rollbacks: switching "versions" is just switching symlinks.
- Multiple versions coexisting: they live at different hashes.
- Reproducibility: same inputs produce the same hash and the same output (in theory; in practice, 99% there).
- Binary caching: if someone else already built exactly these inputs, you can download their output. This is what `cache.nixos.org` does.
- Garbage-collectable: anything not referenced by a "GC root" can be deleted safely.
2026-04-15 11:49:36 +02:00
---
2026-04-15 13:44:59 +02:00
## 5. How You'll Actually Use Nix Day-to-Day
2026-04-15 11:49:36 +02:00
Ordered roughly by power-to-complexity:
2026-04-15 15:47:10 +02:00
1. `nix run nixpkgs#cowsay -- moo`: run a package without installing.
2. `nix shell nixpkgs#jq nixpkgs#ripgrep`: throwaway shell with those tools.
3. Dev shell via `flake.nix`: per-project pinned toolchain. *This is the most common thing flakes are used for.*
4. `nix build`: produce artifacts (binaries, containers, and ISOs).
5. NixOS: declare your whole OS config (packages, services, users, and network) in Nix.
6. home-manager: same idea, at the user level.
2026-04-15 11:49:36 +02:00
Each step up locks in more of your environment. You don't need to adopt all of it at once.
---
2026-04-15 13:44:59 +02:00
## 6. Minimum Useful Commands
2026-04-15 11:49:36 +02:00
```bash
# Language / introspection
nix repl # REPL. Try `:l <nixpkgs>` then `hello`.
nix eval --expr '1 + 1'
nix-instantiate --eval -E '{ a = 1; }.a'
# Running & building
nix run nixpkgs#hello
nix shell nixpkgs#jq
nix build nixpkgs#hello # writes ./result symlink
nix-store --query --references ./result # show closure
# Housekeeping
nix store gc # garbage collect
nix store optimise # dedupe identical files via hardlinks
nix why-depends ./result nixpkgs#glibc # trace dependency chains
```
---
2026-04-15 13:44:59 +02:00
## 7. Gotchas That Trip Up Newcomers
2026-04-15 11:49:36 +02:00
2026-04-15 15:47:10 +02:00
- Lists use spaces, not commas. `[ 1 2 3 ]`. Commas are a syntax error.
- `rec { }` makes an attrset self-referential. Without `rec`, attrs can't reference each other. You'll see this in package definitions.
- Paths vs. strings. `./foo` is a path (copied to store on use). `"./foo"` is a string (not auto-copied). Mixing them causes surprises.
- `builtins.*` is the always-available low-level namespace. `lib.*` (from nixpkgs) is the high-level helper library. Both exist; they're different.
- Evaluation errors are lazy. An error deep in an unused branch only fires when you actually use it. `nix-instantiate --eval --strict` forces full evaluation.
- Hash errors on first build of an FOD: you'll see `got: sha256-…`. Copy that hash into your expression. This is expected workflow.
2026-04-15 11:49:36 +02:00
---
2026-04-15 13:44:59 +02:00
## 8. What to Read or Try Next
2026-04-15 11:49:36 +02:00
- Open `nix repl`, type `:l <nixpkgs>`, then `hello`, `hello.drv`, `hello.outPath`. Poke around.
2026-04-15 13:44:59 +02:00
- Read [`nix.dev`](https://nix.dev), the current best introduction.
2026-04-15 11:49:36 +02:00
- See `003-flakes.md` for the flakes-specific layer on top of all of this.