1
0
Fork 0
mirror of https://github.com/NixOS/nix.dev.git synced 2024-10-18 14:32:43 -04:00
nix.dev/source/guides/best-practices.md
2023-11-27 07:30:36 +01:00

267 lines
7.1 KiB
Markdown

# Best practices
## URLs
The Nix language syntax supports bare URLs, so one could write `https://example.com` instead of `"https://example.com"`
[RFC 45](https://github.com/NixOS/rfcs/pull/45) was accepted to deprecate unquoted URLs and provides
a number of arguments how this feature does more harm than good.
:::{tip}
Always quote URLs.
:::
(rec-expression)=
## Recursive attribute set `rec { ... }`
`rec` allows you to reference names within the same attribute set.
Example:
```{code-block} nix
:class: expression
rec {
a = 1;
b = a + 2;
}
```
```{code-block}
:class: value
{ a = 1; b = 3; }
```
A common pitfall is to introduce a hard to debug error `infinite recursion` when shadowing a name.
The simplest example for this is:
```{code-block} nix
let a = 1; in rec { a = a; }
```
:::{tip}
Avoid `rec`. Use `let ... in`.
Example:
```{code-block} nix
:class: expression
let
a = 1;
in {
a = a;
b = a + 2;
}
```
:::
## `with` scopes
It's still common to see the following expression in the wild:
```{code-block} nix
:class: expression
with (import <nixpkgs> {});
# ... lots of code
```
This brings all attributes of the imported expression into scope of the current expression.
There are a number of problems with that approach:
- Static analysis can't reason about the code, because it would have to actually evaluate this file to see which names are in scope.
- When more than one `with` used, it's not clear anymore where the names are coming from.
- Scoping rules for `with` are not intuitive, see this [Nix issue for details](https://github.com/NixOS/nix/issues/490).
:::{tip}
Do not use `with` at the top of a Nix file.
Explicitly assign names in a `let` expression.
Example:
```{code-block} nix
:class: expression
let
pkgs = import <nixpkgs> {};
inherit (pkgs) curl jq;
in
# ...
```
:::
Smaller scopes are usually less problematic, but can still lead to surprises due to scoping rules.
:::{tip}
If you want to avoid `with` altogether, try replacing expressions of this form
```{code-block} nix
:class: expression
buildInputs = with pkgs; [ curl jq ];
```
with the following:
```{code-block} nix
:class: expression
buildInputs = builtins.attrValues {
inherit (pkgs) curl jq;
};
```
:::
(search-path)=
## `<...>` lookup paths
You will often encounter Nix language code samples that refer to `<nixpkgs>`.
`<...>` is special syntax that was [introduced in 2011] to conveniently access values from the environment variable [`$NIX_PATH`].
[introduced in 2011]: https://github.com/NixOS/nix/commit/1ecc97b6bdb27e56d832ca48cdafd3dbb5185a04
[`$NIX_PATH`]: https://nix.dev/manual/nix/2.18/command-ref/env-common.html#env-NIX_PATH
This means, the value of a lookup path depends on external system state.
When using lookup paths, the same Nix expression can produce different results.
In most cases, `$NIX_PATH` is set to the latest channel when Nix is installed, and is therefore likely to differ from machine to machine.
:::{note}
[Channels](https://nix.dev/manual/nix/2.18/command-ref/nix-channel.html) are a mechanism for referencing remote Nix expressions and retrieving their latest version.
:::
The state of a subscribed channel is external to the Nix expressions relying on it.
It is not easily portable across machines.
This may limit reproducibility.
For example, two developers on different machines are likely to have `<nixpkgs>` point to different revisions of the {term}`Nixpkgs` repository.
Builds may work for one and fail for the other, causing confusion.
:::{tip}
Declare dependencies explicitly using the techniques shown in [](pinning-nixpkgs).
Do not use lookup paths, except in minimal examples.
:::
Some tools expect the lookup path to be set. In that case:
::::{tip}
Set `$NIX_PATH` to a known value in a central location under version control.
:::{admonition} NixOS
On NixOS, `$NIX_PATH` can be set permanently with the [`nix.nixPath`](https://search.nixos.org/options?show=nix.nixPath) option.
:::
::::
(nixpkgs-config)=
## Reproducible Nixpkgs configuration
To quickly obtain packages for demonstration, we use the following concise pattern:
```nix
import <nixpkgs> {}
```
However, even when `<nixpkgs>` is replaced as shown in [](pinning-nixpkgs), the result may still not be fully reproducible.
This is because, for historical reasons, the [Nixpkgs top-level expression] by default impurely reads from the file system to obtain configuration parameters.
Systems that have the appropriate files populated may end up with different results.
[Nixpkgs top-level expression]: https://github.com/NixOS/nixpkgs/blob/master/default.nix
It is a well-known problem that can't be resolved without breaking existing setups.
:::{tip}
Explicitly set [`config`](https://nixos.org/manual/nixpkgs/stable/#chap-packageconfig) and [`overlays`](https://nixos.org/manual/nixpkgs/stable/#chap-overlays) when importing Nixpkgs:
```nix
import <nixpkgs> { config = {}; overlays = []; }
```
:::
This is what we do in our tutorials to ensure that the examples will behave exactly as expected.
We skip it in minimal examples reduce distractions.
## Updating nested attribute sets
The [attribute set update operator](https://nix.dev/manual/nix/2.18/language/operators.html#update) merges two attribute sets.
Example:
```{code-block} nix
:class: expression
{ a = 1; b = 2; } // { b = 3; c = 4; }
```
```{code-block} nix
:class: value
{ a = 1; b = 3; c = 4; }
```
However, names on the right take precedence, and updates are shallow.
Example:
```{code-block} nix
:class: expression
{ a = { b = 1; }; } // { a = { c = 3; }; }
```
```{code-block} nix
:class: value
{ a = { c = 3; }; }
```
Here, key `b` was completely removed, because the whole `a` value was replaced.
:::{tip}
Use the [`pkgs.lib.recursiveUpdate`](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.attrsets.recursiveUpdate) Nixpkgs function:
```{code-block} nix
:class: expression
let pkgs = import <nixpkgs> {}; in
pkgs.lib.recursiveUpdate { a = { b = 1; }; } { a = { c = 3;}; }
```
```{code-block} nix
:class: value
{ a = { b = 1; c = 3; }; }
```
:::
## Reproducible source paths
```{code-block} nix
:class: expression
let pkgs = import <nixpkgs> {}; in
pkgs.stdenv.mkDerivation {
name = "foo";
src = ./.;
}
```
If the Nix file containing this expression is in `/home/myuser/myproject`, then the store path of `src` will be `/nix/store/<hash>-myproject`.
The problem is that now your build is no longer reproducible, as it depends on the parent directory name.
That cannot declared in the source code, and results in an impurity.
If someone builds the project in a directory with a different name, they will get a different store path for `src` and everything that depends on it.
This can be the cause of needless rebuilds.
:::{tip}
Use [`builtins.path`](https://nix.dev/manual/nix/2.18/language/builtins.html#builtins-path) with the `name` attribute set to something fixed.
This will derive the symbolic name of the store path from `name` instead of the working directory:
```{code-block} nix
:class: expression
let pkgs = import <nixpkgs> {}; in
pkgs.stdenv.mkDerivation {
name = "foo";
src = builtins.path { path = ./.; name = "myproject"; };
}
```
:::