Skip to content

Nix Speedrun

This section will cover some Nix language basics as fast as possible.

Comments

# This is a comment

/*
  This is a block comment
*/

Data types

Every value in Nix has a type. Some basic types are:

16 # integer

3.14 # float

false # boolean

"Hello, world!" # string

''
  This is also a string,
  but over multiple lines!
''

Assign a value to a variable:

myVar = "99";

And then inject it into a string:

''
  I got ${myVar} problems,
  but Nix ain't one.
''

Nix also has compound values. This is a list:

[ 123 "hello" true null [ 1 2 ] ]

You can mix different types in a list. This is an attribute set:

{
  foo = 4.56;
  bar = {
    baz = "this";
    qux = false;
  };
}

An attribute set is like an object. It is a collection of name-value-pairs called attributes. The expression above is equivalent to:

{
  foo = 4.56;
  bar.baz = "this";
  bar.qux = false;
}

Evaluation

In Nix, everything is an expression that evaluates to a value. Create a hello.nix-file with the following content:

"Hello, world!"

Then, evaluate the file:

nix eval --file hello.nix
Hello, world!

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

let
  alice = {
    name = "Alice";
    age = "26";
  };
in
''
  Her name is ${alice.name}.
  She is ${alice.age} years old.
''

Functions

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.

let
  increment = num: num + 1;
in
increment 49

Functions can only have a single argument. For multiple arguments, nest functions:

let
  isAllowedToDrive =
    name: age:
    if age >= 18 then "${name} is eligible to drive." else "${name} is too young to drive yet.";
in
isAllowedToDrive "Charlie" 19

It is common to pass multiple arguments in an attribute set instead. Since Nix is lazily evaluated, you can define multiple bindings in the same let-statement.

let
  add = { a, b }: a + b;
  result = add { a = 34; b = 35; };
in
result

You can also set optional arguments by providing default values:

let
  greet = { greeting ? "Hello", name }: "${greeting}, ${name}!";
in
greet { name = "Bob"; }

Let's look at one last example:

let
  myFunc = { a, b, c }: a + b * c;

  numbers = {
    a = 1;
    b = 2;
    c = 3;
  };

  result = myFunc { a = numbers.a; b = numbers.b; c = numbers.c; };
in
result

Nix provides some syntactical sugar to simplify that function call. The with keyword brings all attributes from an attribute set into the scope:

# ...
  result = with numbers; myFunc { a = a; b = b; c = c; };
# ...

However, this syntax is discouraged. Use inherit instead to explicitly list attributes to bring into the scope:

# ...
  inherit (numbers) a b c;
  result = myFunc { inherit a b c; };
# ...

Builtin functions

Nix provides builtin functions by default through the global builtins constant. For example, builtins.attrNames gives you a list of all attributes of the given attribute set:

builtins.attrNames { a = 1; b = 2; }
# => [ "a" "b" ]

Yes, this means that attribute keys, though defined as variables, are available as strings.

Some builtins are so common that the builtins prefix can be omitted. map is a builtin function that applies a function to each element of a list.

# squares.nix
let
  numbers = [ 5 2 1 4 3 ];
  squares = map (n: n * n) numbers;
in
{
  inherit numbers squares;
}

The import function allows to separate the codebase into multiple files:

# sort.nix
let
  results = import ./squares.nix; # paths have their own type
  inherit (results) squares;
  inherit (builtins) sort lessThan;
in
sort lessThan squares

The sort function can be found in the Nix manual.