- “Nix” the programming language…
- …executed by the “Nix Daemon”…
- …which interacts with the “Nix” CLIs
- Language: Nix
- Daemon:
nix-daemon - CLIs:
nix-<command>andnix <command> - Stdlib:
nixpkgs
- Attribute set (or “attrset”)
- A map of keys and values of
arbitrary types. Key = Value pairs must end with a
;.{ a = 13; b = 12; c = 11; hello = true; } - Lists
- A list of arbitrary types. Can contain mixed types.
Values are space-separated.
[ 13 12 11 "hello" true ]
- Functions
- A function to call. Can both be named
(
fetchFromGitHub), or anonymous.map (x: x * 2) [ 2 4 6 8 10 ] - Strings
- UTF-8 literals.
Two variants: inline (via
"...") and block (via''...''). Block strings expand\n. Both strings support iterpolation with${}(called “dollar-curly”).
- Integers (
5,14,42) - Doubles (
4.15151515) - Booleans (
trueandfalse) - Paths (
/nix/storeor./my-module) - Null (
null)
The semantics of Nix is assignments all the way down.
{
package = pkgs.wine.override {
wineBuild = "wine64";
wineRelease = "staging";
};
}{
package = pkgs.wine.override {
wineBuild = "wine64";
wineRelease = "staging";
};
}package =defines a key with somethingpkgs.wine.override { ... }is a functionwineBuildandwineReleaseare two keys in an attribute set, passed towine.override
let wine = pkgs.wine.override {
wineBuild = "wine64";
wineRelease = "staging";
};
in {
package = wine;
}- Pre-define a set of variables for a given scope
- There are no global variables, only scope-specific bindings
let package = pkgs.wine.override {
wineBuild = "wine64";
wineRelease = "staging";
};
in {
inherit package;
}- Take a value from one scope and copy it to another
- Essentially
{ inherit foo; }is the same as writing{ foo = foo; }.
- Not technically a keyword but a
builtin - Loads and parses the nix expression at the given path
# config.nix
{ logging = "debug"; port = 8080; open = true; }# default.nix
{ name = "my-application-service"; config = import ./config.nix; }- Loads a scope into the following nix expression
- Makes all keys available
- Considered a little bit controvertial
with lib;
{
src = with pkgs; fetchFromGitHub {
owner = "spacekookie";
repo = "ddos";
sha256 = fakeSha256;
};
}nix-repl> { productRelease = currentYear - 8; currentYear = 2022; }.productRelease
error: undefined variable 'currentYear'
at «string»:1:20:
1| { productRelease = currentYear - 8; currentYear = 2022; }.productRelease
| ^- By default attribute sets are not recursively self-referencial (for performance reasons)
- To enable this, mark an attribute set with the
reckeyword - Alternatively you can usually always use a
let ... inblock
nix-repl> rec { productRelease = currentYear - 8; currentYear = 2022; }.productRelease
2014Let’s look at a function which accepts an attribute set.
let
xySum = attrs: attrs.x + attrs.y;
in
xySum { x = 5; y = 7; }- What happens when we call this function without an attribute set? Or with missing values?
What happens when we…
- pass the wrong type (
xySum 5) error: value is an integer while a set was expected- pass an incomplete set (
xySum { x = 5; }) error: attribute 'y' missing
Furthermore, writing attrs.<value> repeatedly will get annoying
quickly.
Declare the function as accepting an attribute set, with a specific set of keys.
let
xySum = { x, y }: x + y;
in
xySum { x = 5; y = 7; }There are some other options you have for making functions with named parameters (i.e. which accept an attribute set) easier to use.
- Allow additional parameters
{ x, y, ... }: x + y- Assume defaults
{ x, y ? 7 }: x + y- Bind the full set
{ x, y ? 7 } @ set: otherFunction (x + y) set
let
function = { a ? 23, ... } @ args: args;
in
function { }What does this function return?
Isn’t that delightfully confusing?
let
function = { a ? 23, ... } @ args: { inherit a; } // args;
in
function { }- Nix uses a lookup path for loading modules called
NIX_PATH - Arbitrary keys and values can exist
- Values can be retrieved via the “diamond reference”
nix-repl> <nixpkgs>
/home/sys
nix-repl> <modules>
/home/sys/modulesSometimes you want to merge two attribute sets, or append one list onto another!
{ a = 13; b = 12; } // { c = 11; }- results in
{ a = 13; b = 12; c = 11; } [ 13 12 ] ++ [ 11 ]- results in
[ 13 12 11 ]
Let’s take the example builder from earlier. Can we understand what happens here?
with import <nixpkgs> {};
let
myPython = pkgs.python3.withPackages (pypkgs:
with pypkgs; [ request flask prometheus_client pendulum ]);
in
stdenv.mkDerivation {
name = "prometheus-weather-gov";
src = ./.;
buildInputs = with pkgs.python3.pkgs; [
myPython mypy flake8 black
];
}