This repository has been archived on 2026-08-30. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
dotfiles/docs/nix.md

3.9 KiB

Nix Conventions

This document records local Nix style and design conventions. Prefer these conventions when changing this flake.

Names

Reserve lib for nixpkgs.lib

Use lib only for the base nixpkgs.lib value. Do not use lib for project helper functions. Use dotfiles.lib for pure project helpers passed through module arguments. Use dotfiles.<name> for project assembly helpers passed through module arguments.

Avoid:

{
  lib = dotfilesHelpers;
}

Use:

{
  lib = nixpkgs.lib;
  dotfiles.lib = dotfilesHelpers;
}

Imports

Use closed interfaces for helper imports

Use exact function argument patterns when an imported helper file has a closed interface. A helper file has a closed interface when the importer owns the argument set. The imported file must name each accepted attribute. This rejects stale or accidental extra attributes.

Avoid:

{
  inputs,
  lib,
  self,
  ...
}:

Use:

{
  inputs,
  lib,
  self,
}:

Pass import arguments explicitly

Pass only the values an imported helper file needs. Bind lib to the base nixpkgs.lib value at the call site. Use inherit for values that keep the same name.

Avoid:

import ./flake inputs

Use:

import ./flake {
  inherit inputs self;
  lib = nixpkgs.lib;
}

Keep NixOS module interfaces open

NixOS modules must accept extra arguments because the module system owns their argument shape. Name the arguments the module uses. Keep ... so the module accepts standard module arguments and special arguments.

Avoid:

{
  config,
  lib,
  pkgs,
  registry,
}:

Use:

{
  config,
  lib,
  pkgs,
  registry,
  ...
}:

Use @ for forwarding wrappers

Use @ when a wrapper must pass the complete argument set to another module function. Name the arguments the wrapper uses before .... Forward the bound argument set unchanged.

Avoid:

{ dotfiles, ... }:
dotfiles.guest { name = "sample"; } { inherit dotfiles; }

Use:

args@{ dotfiles, ... }:
dotfiles.guest { name = "sample"; } args

let Bindings

Use let for important intermediate values

Use a let binding when it names an important intermediate value. Use a let binding when it avoids repeating a complex expression. Do not use a let binding only to shorten a standard library call.

Avoid:

let
  inherit (lib) mapAttrs;
in
mapAttrs f attrs

Use:

lib.mapAttrs f attrs

Standard Library Calls

Qualify standard library calls in flake assembly

Prefer qualified standard library calls inside flake assembly code. Use lib.mapAttrs instead of inheriting mapAttrs from lib. This keeps the source of helper functions visible.

Avoid:

let
  inherit (lib) mapAttrs;
in
mapAttrs f attrs

Use:

lib.mapAttrs f attrs

Flake Assembly

Keep assembly code under flake/

Keep flake assembly code under flake/. Keep flake.nix focused on inputs and public outputs. A public output is any attribute returned by the flake outputs function. Another flake can reference a public output through inputs.<name>.<output>. Tools such as nix flake show also display public outputs.

Avoid:

# flake.nix
let
  mkHosts = hostsDir: ...;
in
{
  nixosConfigurations = mkHosts (self + "/hosts");
}

Use:

# flake.nix
let
  flakeAssembly = import ./flake {
    inherit inputs self;
    lib = nixpkgs.lib;
  };
in
{
  nixosConfigurations = flakeAssembly.mkHosts (self + "/hosts");
}

Expose only supported flake outputs

Use public outputs for the supported interface of this flake. Do not expose assembly helpers as public outputs unless an external consumer needs them. Keep internal helpers in flake/ or dotfiles special arguments.

Avoid:

{
  lib = flakeAssembly;
  nixosConfigurations = flakeAssembly.mkHosts (self + "/hosts");
}

Use:

{
  nixosConfigurations = flakeAssembly.mkHosts (self + "/hosts");
}