Intro in Nix & Flakes

This is yet another attempt to explain flakes based on what I personally used to misunderstand.

flake.nix is just a simple set

What some people fail to explain that the set described by flake.nix, to be precise,

  inputs.quack1.url = "aaa";
  inputs.quack2.url = "aaa";
  outputs = { quack1, quack2, ... }: {
is merely an actual set. Not special. You can add any fields there.

But nix3 commands interpret certain fields specially

Flake is merely a set, but nix3 commands, like nix develop, nix shell, and so on, interpret it in a certain way. When they evaluate a flake, they collect all input URLs, fetch them, evaluate them, and substitute into outputs. The evaluated value of outputs is the evaluated value of flake according to nix3 commands.

Let's play around flake

Let's create a very simple flake:

  outputs = { self, ... }: {
    sum-of-two = a: b: a + b;
There's no inputs, and there's only one output field. Let's import it as a nix file if you don't believe me:
$> nix repl

nix-repl> f = import ./flake.nix

nix-repl> f
{ outputs = «lambda @ /home/goose/trash/flake-playground/sum-of-two/flake.nix:2:13»; }
See? This is exactly how we define it. We defined it as a set with one attribute. Outputs is a function, just like we defined. We can even evaluate it ourselves, manually. Look:
nix-repl> (f.outputs { self = f; }).sum-of-two 4 5
But all because it has a special format (and name flake.nix), we can also load it as flake into our repl:
nix-repl> :lf .
Added 9 variables.
It evaluated the flake and put its evaluated value into variable called outputs. Let's see its value:
nix-repl> outputs
{ sum-of-two = «lambda @ /nix/store/qzf8wd8nj8ajsrrxazs338ysz62f9yai-source/flake.nix:3:18»; }

nix-repl> outputs.sum-of-two 4 5
Yep, it evaluted to what we expected. We didn't depend on the input, so it just returned the set that we return in function outputs.

What about inputs?

Let's create another flake in another folder. I call the older one two-of-sum and the new one main. The folder structure is:

$> find .
And ./main/flake.nix is defined:
$> cat flake.nix
  inputs.sum.url = "path:/home/goose/trash/flake-playground/sum-of-two";
  outputs = { sum, ... }: {
    heh = sum.sum-of-two 4 5;
Now we added the other flake as input and return an set where one field is sum of 4 and 5 taken from the other flake. Now we need to lock the file to ensure that the inputs don't change on their own:
$> nix flake lock
warning: creating lock file '/home/goose/trash/flake-playground/main/flake.lock'
Now let's enter the flake, evaluate it, and see the value:
$> nix repl
Welcome to Nix 2.13.3. Type :? for help.

nix-repl> :lf .
Added 9 variables.

nix-repl> outputs
{ heh = 9; }
See? sum input was evaluated and substituted into outputs. The evaluated flake sum has field sum-of-two, which is what we call inside in ours.

What's up with shells and nixosConfigurations and packages?

Basically, commands like nix develop, nix shell etc. merely evaluate the flake and interpret certain fields. Which fields? Depends on the command. For example, nix develop looks for field/attribute devShells. To see which commands expect which fields, see output schema.