primitives

strings, numbers, paths, booleans, lists, and null.

nine types. seven primitives, plus attribute sets and functions (which get their own pages).

builtins.typeOf returns one of these nine strings
string"hello"double-quoted or multi-line with ''
integer4264-bit signed. overflow is a hard error
float3.1464-bit. rare in practice
booleantruetrue or false. lowercase
nullnulla distinct type, separate from false and 0
path./srcresolves relative to the file, not the shell
list[ 1 2 3 ]ordered, heterogeneous. spaces, no commas
set{ a = 1; }next page
lambdax: x + 1two pages out

fire up nix repl and follow along.

strings

"hello" + " " + "world"
# "hello world"

interpolation with ${}:

let name = "curl"; in "package: ${name}"
# "package: curl"

the expression inside ${} must evaluate to a string. nix does not silently coerce integers. "count: ${42}" is an error. use toString:

let n = 42; in "count: ${toString n}"
# "count: 42"

multi-line strings

two single quotes ('') delimit multi-line strings. nix strips common leading whitespace:

''
  first line
  second line
''
# "first line\nsecond line\n"

this matters when you embed shell scripts in derivations:

buildPhase = ''
  mkdir -p $out/bin
  gcc -o $out/bin/hello hello.c
'';

the two spaces before mkdir are relative to the delimiters. they get stripped.

numbers

1 + 2   # 3
7 / 2   # 3

integer division truncates.

floats exist (1.0 + 2.5 gives 3.5) but are rare. you will almost never write one.

booleans

true && false     # false
!true             # false

if true then "yes" else "no"
# "yes"

if is an expression. it returns a value. the else branch is required.

null

null == null    # true
null == false   # false

its own type. no implicit conversions.

paths

paths are not strings.

./src
# /home/karol/project/src

./.
# /home/karol/project

relative paths resolve against the directory of the nix file that contains them. not the working directory of your shell. ./src in a default.nix always points to the src next to that file, regardless of where you run nix-build.

interpolate a path into a string and nix copies it into the store:

"${./hello.c}"
# "/nix/store/...-hello.c"

that is how source files enter the build system.

angle-bracket paths

import <nixpkgs>

<nixpkgs> is a lookup against NIX_PATH. it resolves to wherever your system has nixpkgs. flakes replace this, but you will see it in older code.

lists

ordered, heterogeneous, space-separated. no commas.

[ 1 2 3 ]
[ "hello" 42 true null ]

[ 1 2 ] ++ [ 3 4 ]
# [ 1 2 3 4 ]

++ concatenates. no index operator. builtins.elemAt does it:

builtins.elemAt [ "a" "b" "c" ] 1
# "b"

lists show up in buildInputs, propagatedBuildInputs, meta.platforms. you rarely need to index into them.

type checking

builtins.typeOf "hello"      # "string"
builtins.typeOf 42           # "int"
builtins.typeOf ./src        # "path"
builtins.typeOf { a = 1; }   # "set"
builtins.typeOf (x: x)       # "lambda"

attribute sets are "set". functions are "lambda". these show up in error messages.

attribute sets is the data structure everything in nix revolves around: nesting, merging, recursive sets, let...in, with, and inherit.