Recursive sets are like normal attribute sets, but the attributes can refer to each other.

rec-attrset = rec { [ name = expr ; ]... }

Example:

rec {
  x = y;
  y = 123;
}.x

This evaluates to 123.

Note that without rec the binding x = y; would refer to the variable y in the surrounding scope, if one exists, and would be invalid if no such variable exists. That is, in a normal (non-recursive) set, attributes are not added to the lexical scope; in a recursive set, they are.

Recursive sets of course introduce the danger of infinite recursion. For example, the expression

rec {
  x = y;
  y = x;
}.x

will crash with an infinite recursion encountered error message.

A let-expression allows you to define local variables for an expression.

let-in = let [ identifier = expr ]... in expr

Example:

let
  x = "foo";
  y = "bar";
in x + y

This evaluates to "foobar".

When defining an attribute set or in a let-expression it is often convenient to copy variables from the surrounding lexical scope (e.g., when you want to propagate attributes). This can be shortened using the inherit keyword.

Example:

let x = 123; in
{
  inherit x;
  y = 456;
}

is equivalent to

let x = 123; in
{
  x = x;
  y = 456;
}

and both evaluate to { x = 123; y = 456; }.

Note

This works because x is added to the lexical scope by the let construct.

It is also possible to inherit attributes from another attribute set.

Example:

In this fragment from all-packages.nix,

graphviz = (import ../tools/graphics/graphviz) {
  inherit fetchurl stdenv libpng libjpeg expat x11 yacc;
  inherit (xorg) libXaw;
};

xorg = {
  libX11 = ...;
  libXaw = ...;
  ...
}

libpng = ...;
libjpg = ...;
...

the set used in the function call to the function defined in ../tools/graphics/graphviz inherits a number of variables from the surrounding scope (fetchurl ... yacc), but also inherits libXaw (the X Athena Widgets) from the xorg set.

Summarizing the fragment

...
inherit x y z;
inherit (src-set) a b c;
...

is equivalent to

...
x = x; y = y; z = z;
a = src-set.a; b = src-set.b; c = src-set.c;
...

when used while defining local variables in a let-expression or while defining a set.

Functions have the following form:

pattern: body

The pattern specifies what the argument of the function must look like, and binds variables in the body to (parts of) the argument. There are three kinds of patterns:

Note that functions do not have names. If you want to give them a name, you can bind them to an attribute, e.g.,

let concat = { x, y }: x + y;
in concat { x = "foo"; y = "bar"; }

Conditionals look like this:

if e1 then e2 else e3

where e1 is an expression that should evaluate to a Boolean value (true or false).

Assertions are generally used to check that certain requirements on or between features and dependencies hold. They look like this:

assert e1; e2

where e1 is an expression that should evaluate to a Boolean value. If it evaluates to true, e2 is returned; otherwise expression evaluation is aborted and a backtrace is printed.

Here is a Nix expression for the Subversion package that shows how assertions can be used:.

{ localServer ? false
, httpServer ? false
, sslSupport ? false
, pythonBindings ? false
, javaSwigBindings ? false
, javahlBindings ? false
, stdenv, fetchurl
, openssl ? null, httpd ? null, db4 ? null, expat, swig ? null, j2sdk ? null
}:

assert localServer -> db4 != null; 
assert httpServer -> httpd != null && httpd.expat == expat; 
assert sslSupport -> openssl != null && (httpServer -> httpd.openssl == openssl); 
assert pythonBindings -> swig != null && swig.pythonSupport;
assert javaSwigBindings -> swig != null && swig.javaSupport;
assert javahlBindings -> j2sdk != null;

stdenv.mkDerivation {
  name = "subversion-1.1.1";
  ...
  openssl = if sslSupport then openssl else null; 
  ...
}

The points of interest are:

  1. This assertion states that if Subversion is to have support for local repositories, then Berkeley DB is needed. So if the Subversion function is called with the localServer argument set to true but the db4 argument set to null, then the evaluation fails.

    Note that -> is the logical implication Boolean operation.

  2. This is a more subtle condition: if Subversion is built with Apache (httpServer) support, then the Expat library (an XML library) used by Subversion should be same as the one used by Apache. This is because in this configuration Subversion code ends up being linked with Apache code, and if the Expat libraries do not match, a build- or runtime link error or incompatibility might occur.

  3. This assertion says that in order for Subversion to have SSL support (so that it can access https URLs), an OpenSSL library must be passed. Additionally, it says that if Apache support is enabled, then Apache's OpenSSL should match Subversion's. (Note that if Apache support is not enabled, we don't care about Apache's OpenSSL.)

  4. The conditional here is not really related to assertions, but is worth pointing out: it ensures that if SSL support is disabled, then the Subversion derivation is not dependent on OpenSSL, even if a non-null value was passed. This prevents an unnecessary rebuild of Subversion if OpenSSL changes.

A with-expression,

with e1; e2

introduces the set e1 into the lexical scope of the expression e2. For instance,

let as = { x = "foo"; y = "bar"; };
in with as; x + y

evaluates to "foobar" since the with adds the x and y attributes of as to the lexical scope in the expression x + y. The most common use of with is in conjunction with the import function. E.g.,

with (import ./definitions.nix); ...

makes all attributes defined in the file definitions.nix available as if they were defined locally in a let-expression.

The bindings introduced by with do not shadow bindings introduced by other means, e.g.

let a = 3; in with { a = 1; }; let a = 4; in with { a = 2; }; ...

establishes the same scope as

let a = 1; in let a = 2; in let a = 3; in let a = 4; in ...

Comments can be single-line, started with a # character, or inline/multi-line, enclosed within /* ... */.

A quasi-constant which will be replaced with an attribute set describing the location where __curPos was used, with attributes file, line, and column. For example, import ./file.nix will result in

{
  column = 1;
  file = "/path/to/some/file.nix";
  line = 1;
}

assuming file.nix contains nothing but __curPos.

In context without a source file (such as nix-repl), it will always be replaced with null:

nix-repl> __curPos
null

While it may vaguely look like a builtin, this is a very different beast that is handled directly by the parser. It thus cannot be shadowed, bound to a different name, and is also not available under builtins.

nix-repl> let __curPos = "no"; in __curPos
null

Despite this __curPos, much like or, may still be used as an identifier, it is only treated specially when it appears as an unqualified name:

nix-repl> { __curPos = 1; }.__curPos
1

or is used in Attribute selection, where it is a keyword.

However, it is not a keyword in some other contexts, and can be used as a binding name in attribute sets, let-bindings, non-initial function application position, and as a label in attribute paths.

Its use as anything other than a keyword is discouraged.