Skip to content

Instantly share code, notes, and snippets.

@PedroHLC
Last active June 22, 2024 21:14
Show Gist options
  • Save PedroHLC/e3d3b549bc39f780cd17053d80193cce to your computer and use it in GitHub Desktop.
Save PedroHLC/e3d3b549bc39f780cd17053d80193cce to your computer and use it in GitHub Desktop.
The most basic tutorial for Nix
  • Nix (uppercase N): a Domain-specific language with meta language roots;

  • nix (lowercase N): a CLI toolkit developed by NixOS Foundation which core's to evaluating Nix;

  • Nix's ecossystem (Written with Nix or using nix):

    • Nixpkgs: repository of all united efforts related to packaging programs and creating a system to run them;
    • NixOS: a Linux-distro, subproduct of Nixpkgs;
    • Home-manager: a dotfiles manager using Nixpkgs;
    • Impermanence: Immutable RootFS for NixOS;
    • Flake: Declarative description (artifacts and depedencies) for Nix's projects (our take on NPM's package.json);
    • Anything else under github:NixOS, under github:nix-community, public repositories with a /flake.nix, or public repositories with a /default.nix.
  • Store: where pre-build binaries for Nix are stored locally;

  • Cache: where pre-build binaries for Nix are stored remotely.

Syntax

Syntax basic aspects

  • For starters, Nix has all that basic types you'll expect from JSON:

    • Null: null;
    • Boolean: true and false;
    • Integers: 123 and -123;
    • Floats: -0.123 and -12.3;
    • Lists:
      • without comma-separator,
      • [ 10 20 30 ];
    • Objects:
      • in Nix its called Attrset,
      • instead of comma the separator is ;,
      • their keys are strings, but you can omit the quotes,
      • {
          name = "Pedro";
          "last name" = "HLC";
          age = 29;
          adult = true;
        }
  • Besides that, we have basic mathmetical and logical operators:

    • 10 + 10 == 20;
    • 30 - 20 == 10
    • 2 * 4 == 8;
    • (2 + 2) / 2 == 2;
    • true != false;
    • true && true;
    • false || true;
  • Nix also have if ... else .. constructor: if x || y then 100 else -50;

  • String templates: "I like ${flavor} ice cream"

Sytax shortcuts

1 - The most basic shortcut you'll find in Nix is the with keyword, and it's used like this:

  • with { a = 10; b = 20; }; a + b;
  • See how a and b can now be referenced beyond the original attrset.

2 - We also have a similar interesting shortcut with rec keywork:

  • rec { a = 10; b = 20; c = a + b; }
  • See how a and b were allowed to be referenced inside the attrset itself.

3 - What if we combine those keywords? Then we get the let ... in constructor:

  • let
      a = 10;
      b = a + 10;
    in
    a + b
  • See how a and b were allowed to be referenced inside and outside the constructors' block.

4 - Nested attrset have a sugar for them:

  • { x = { y = { z = 0; }; }; }
  • can also be written as:
  • { x.y.z = 0; }

Lambda/functions

  • In Nix, lambdas are always unary functions, like:

    •  a: a + 10
    • This is a single function that takes a single argument and adds 10 to it.
  • But you can nest them to achieven n-ary functions, like:

    •  a: b: c: c * (a + b)
  • Lambda parameters can pattern-match Attrsets, like this:

    •  a: { b, c }: d: (c + d) * (a + b)
  • While pattern-matching you can add "default values" to the Attrset fields, like this:

    •  a: { b ? 10, c ? 20 }: d: (c + d) * (a + b)
  • The "default values" can be dynamic and recursive:

    •  a:
       { b ? a + 10, c ? if b > 10 then 20 else 30 }:
       d:
       (c + d) * (a + b)
  • You can't "name" your lambda as most language's allow with functions, but you can attribute them in let ... in blocks or wrap them as Attrset's values.

The standard library.

Nix comes bundled with a lot of hardcoded lambdas in a attrset called builtins, the contents of this attrset are available in all the scopes so you can omit attrset..

One of these lambdas is named import and is probably the most important one, you can load any file with Nix-valid-syntax, like:

# File "./first-number.nix" contains: 10
# File "./first-number.nix" contains: 10
# File "./machine.nix" contains: { operation = builtins.sum; toString = a: b: sum: "The resulf of ${a} + ${b} is ${sum}"; }
let
  firstNumber = import ./first-number.nix;
  secondNumber = import ./second-number.nix;
  machine = import ./machine.nix;
in
machine.toString firstNumber secondMachine (machine.operation firstNumber secondNumber)

For checking the entire list of builtins go through the manual, and this is your only and last homework.

Some stuff in Nix, that are not absolutely truth and must be clarified before continuing:

1 - Nix is a pure-language (functional programming language context)

This is a half-truth, how to make it a full-truth at the end

A pure-function is a function that given the same arguments will always output the same result.

That is not the case for traditional Nix usage with nix. Some builtins like builtins.getEnv will return different values depending on outside factors:

$ EXPR='builtins.getEnv "SDL_VIDEODRIVER"'

$ SDL_VIDEODRIVER=wayland nix-instantiate --eval -E $EXPR
"wayland"

$ SDL_VIDEODRIVER=x11 nix-instantiate --eval -E $EXPR
"x11"

Full-truth condition: One can achieve a real pure-language Nix by adding Flake to the project.

2 - Nix is reproducible

This is a half-truth, how to make it a full-truth at the end

The resulting builds of nix is usually as much-reproducible as a Docker.

Reproducible means you get the same result, bit-for-bit, when running a build in different circumstances. This is not current usage of Nix in Nixpkgs does, instead they're making sure all the project's files, dependencies' files and steps involved in building are the exactly same for everyone.

Full-truth condition: There are efforts in-place to make Nixpkgs more reproducible.

3 - NixOS is immutable

This is a half-truth, how to make it a full-truth at the end

The Store is read-only most of the time, your RootFS will be filled with symbolic links to read-only and binding mounts to read-only directories. But no, you won't booot directly into a immutable filesystem in your first NixOS-try.

Full-truth condition: One can use Impermanence to obtain an immutable RootFS while picking which sub-directories should be kept mutable.

4 - Nix's packages runs anywhere

This is a half-truth, how to make it a full-truth at the end

It's a fact that "nix" is available almost everywhere. It's a fact that Nix dependencies are isolated and version-strict in a way that they don't conflict with the host's libraries and in-between Nix projects. It's a fact that these make everything more portable.

But some projects needs to interact with hosts' libraries and files, like everything using OpenGL, and will need tweaks to run. And sometimes you'll need some external tool like bubblewrap to be able to place the store in /nix.

Full-truth condition: There are projects like nixGL and nix-bundle that helps in making portability real.

Assuming the following about the reader:

  • You're coming from ArchLinux, Fedora or Gentoo: this is surely not your first time trying Linux and you been confortable on experimenting outside of Ubuntu world;

  • You have some background of programming: either JavaScript, Python, C, etc...

  • You know common programming-languages' mathematical and logic operators (e.g.: ==, !=).

  • You surely knows Nix is declarative: you might not fully know what this word means, but someone has been telling you all about it lately.

  • You have your entire day free to learn Nix in baby steps;

  • You know what JSON is;

  • You know what a Unary function is;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment