Nix language

The Nix expression language is used all over in Nix and the Nix OS.

https://nixos.wiki/wiki/Nix_Expression_Language

Expressions

In Nix, everything is an expression. There are no statements.

The nix repl

You can experiment with the Nix language using the nix repl, e.g.:

$ nix repl
nix-repl> _

Immutable

In Nix, all values are immutable.

Types

While Nix is not statically typed, it is strongly typed. To do an operation between values of different types, for example, you have to explicitly convert one value to the other type.

Identifiers

Unlike most other languages, identifiers in Nix can include the “-” character. (If you want to subtract, add in some whitespace to make things unambiguous.)

Attribute sets

Confusingly Nix has a data type formally called attribute sets, but usually just called sets, that are similar to Python dictionaries or JavaScript objects: a collection of named values, called “attributes”.

Literal sets are written as a sequence of assignments, each one terminated by a semicolon, all wrapped in curly braces.

Use .key to access a value from the set, similar to using [key] in Python.:

var1 = { key1="value1";  key2="value2"; }
var1.key2
> "value2"

You can access an attribute and provide a default in case the set doesn’t have that attribute:

val = set.a or 13

If you need to refer to another element in the set while writing the set, you can use recursive sets:

val2 = rec { a=1; b=2; c=a + 4; }

If expressions

The expression if A then val1 else val2 has the value val1 if A is true and otherwise val2. Since all expressions must have a value, if expressions must always have an else.

Let expressions

Like lisp, you can use let to temporarily define local variables for use inside an expression:

let a = 3; b = 4; in a + b
> 7

You can refer to variables in the let expression when assigning variables, like with recursive attribute sets, or let* in Lisp.

With expressions

This is just a syntactic shortcut:

nix-repl> longName = { a = 3; b = 4; }
nix-repl> longName.a + longName.b
7
nix-repl> with longName; a + b
7

Warning: if there’s already a variable with the same name as the attribute set used in the with, the outer variable is not shadowed and continues to refer to that outer variable.

It seems safer to me to write something like:

let l=LongName; in l.a + l.b

Strings

Literal strings are written either with double quotes, or two single quotes at each end:

val1 = "Hello, world."
val2 = ''And hello to you.''

You can use one kind of quotes inside a string written using the other kind.

In strings, you can interpolate string values using ${}:

val3 = "Hello, ${username}"

Escaping ${…} within double quoted strings is done with the backslash. Within two single quotes, it’s done with ‘’ (!):

nix-repl> "\${foo}"
"${foo}"
nix-repl> ''test ''${foo} test''
"test ${foo} test"

Paths

Nix has a special type for file names and paths, called paths. You don’t need to put quotes around them:

filename = ./a/b/foo.txt

but note that paths are automatically resolved to absolute paths, so after the above, the actual path stored in filename might be /var/www/a/b/foo.txt.

You can also write literally <nixpkgs/stuff> and it’ll resolve to the stuff directory under your NIX_PATH.

If you have a string like "/foo/bar" that you want to use as a path, you have to convert it by writing:

mypath = /. + builtins.toPath "/foo/bar"

(https://nixos.wiki/wiki/Nix_Expression_Language#Convert_a_string_to_an_.28import-able.29_path)

Lists

Lists are written as a series of expressions separated by space (NOT COMMA) and with square brackets around them:

[ 2 "foo" true (2+3) ]

Lists are immutable, so adding or removing elements returns a new list.

Functions

Functions are all unnamed lambda functions, sometimes assigned to variables, and all have a single argument. They have the form:

argument: nixExpression

so for example:

funca = argument: argument * argument

Call a function by writing the name of the function (well, the name of the variable the function was assigned to), a space, then the expression to pass as the functions argument. No parentheses or commas.

e.g. funca 12 would square its input and have the value 144.

For multiple arguments:

funcb = arg1: arg2: nixExpression
funcb 1 34

You can create partials:

funcc = funcb 1

would be a new function that takes one argument and returns the value of funcb 1 <new argument>.

Destructuring

You can pass a set into a function and declare the keys the set should have:

funcd = {a, b}: a + b
funcd {a=12; b=13;}
> 25

You can declare default values for some or all the keys:

add_a_b = { a ? 1, b ? 2 }: a + b
add_a_b {}
3
add_a_b {a=5;}
7

Note

This looks like Python named arguments - cool!

If the set passed in the call has additional keys, the call will fail unless you say to allow that by adding .... You can access any extra keys by supplying a name with @:

add_a_b = args@{ a, b, ... }: a + b + args.c
add_a_b { a=5; b=2; c=10; }
17

Import

The import keyword loads a Nix expression from a file, evaluates it, and returns the result:

a = import a/b/path

If the result is a function, you can of course call it immediately:

x = import <nixpkgs/myfile.nix> {a=1; b=12;}

Derivations and .drv files

There’s a built-in function named derivation. It takes an argument set with at least three elements:

  • name: a name for the derivation

  • system: is the name of the system in which the derivation can be built. For example, x86_64-linux.

  • builder: it is the binary program that builds the derivation.

Aside: the currently running system is available as builtins.currentSystem:

builtins.currentSystem
> x86_64-linux

In nix repl, calling derivation with a derivation expression will not build the derivation (e.g. compile the package), but it will create the .drv file.

(The .drv file is kind of like a compiled .c file - it’s a more compact representation of the derivation, but it doesn’t do anything by itself.)

In the nix repl, the value returned by calling derivation is an attribute set. It will be printed something like:

nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; }
nix-repl> d
«derivation /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv»

because Nix does something special if an attribute set has an attribute named “type”, and this one has type = "derivation", but it’s really just an attribute set.

We can refer elsewhere to a derivation by its outPath string, which we can easily extract using builtins.toString:

nix-repl> builtins.toString d
"/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname"

(builtins.toString just returns the outPath attribute after converting it to a string.)

You can pretty-print the .drv file:

$ nix show-derivation /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv
{
  "/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv": {
    "outputs": {
      "out": {
        "path": "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname"
      }
    },
    "inputSrcs": [],
    "inputDrvs": {},
    "platform": "mysystem",
    "builder": "mybuilder",
    "args": [],
    "env": {
      "builder": "mybuilder",
      "name": "myname",
      "out": "/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname",
      "system": "mysystem"
    }
  }
}

There’s an “out” path that tells us where Nix would put the build output if we were to build it.

If you really want to build a derivation in nix repl, use the nix repl command :b.

nix-repl> d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; }
nix-repl> :b d
[...]
these derivations will be built:
  /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv
building path(s) `/nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname'
error: a `mysystem' is required to build `/nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv', but I am a `x86_64-linux'

Outside nix repl, you can “realize” (build) a derivation with nix-store -r, e.g.:

$ nix-store -r /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv