Aller au contenu
Administration Linux medium

Le langage Nix : types, fonctions, attrsets et expressions

18 min de lecture

Le langage Nix est un langage fonctionnel pur conçu pour décrire des paquets et des configurations de manière reproductible. Tout y est expression : un nombre, une fonction, un fichier de configuration — chaque élément retourne une valeur, rien ne produit d’effet de bord. Ce guide vous fait découvrir chaque concept fondamental en le testant immédiatement dans le REPL ou avec nix eval.

  • Manipuler les 7 types primitifs de Nix (entiers, flottants, chaînes, booléens, null, chemins, fonctions)
  • Structurer des données avec les attribute sets (attrsets) et les listes
  • Définir et appeler des fonctions (simples, curryfiées, à arguments nommés)
  • Utiliser let...in, with, inherit et if-then-else
  • Transformer des données avec les builtins (map, filter, attrNames…)
  • Écrire des expressions complètes prêtes pour shell.nix ou flake.nix

Le langage Nix est le socle de tout l’écosystème : chaque paquet est décrit par une expression Nix, chaque shell.nix, chaque flake.nix, chaque module NixOS est du code Nix. Comprendre le langage vous permet de :

  • lire et modifier les fichiers shell.nix ou flake.nix de vos projets,
  • écrire vos propres dérivations pour empaqueter des logiciels,
  • déboguer un environnement de développement qui ne se comporte pas comme prévu,
  • comprendre les exemples de la documentation nixpkgs et NixOS,
  • personnaliser une configuration NixOS module par module.

Le REPL (Read-Eval-Print Loop) est un terminal interactif pour tester des expressions Nix. Lancez-le avec :

Fenêtre de terminal
nix repl --expr '{}'

Tapez une expression, Nix l’évalue et affiche le résultat. Tapez :q pour quitter.

Vous pouvez aussi évaluer des expressions ponctuelles directement dans le terminal avec :

Fenêtre de terminal
nix eval --expr '1 + 2'
Résultat
3

Tous les exemples de ce guide sont testables avec l’une ou l’autre méthode.

Nix possède 7 types de base. Vous pouvez vérifier le type de n’importe quelle valeur avec builtins.typeOf :

Fenêtre de terminal
nix eval --expr '1 + 2'
Résultat
3
Fenêtre de terminal
nix eval --expr '7 / 2'
Résultat
3

La division entre entiers est entière (pas d’arrondi, troncature). Pour obtenir un résultat décimal :

Fenêtre de terminal
nix eval --expr '7.0 / 2.0'
Résultat
3.5
Fenêtre de terminal
nix eval --expr 'builtins.typeOf 42'
Résultat
"int"

Les chaînes sont délimitées par des guillemets doubles :

Fenêtre de terminal
nix eval --expr '"hello" + " world"'
Résultat
"hello world"

L’opérateur + concatène les chaînes (et additionne les nombres — Nix distingue par le type des opérandes).

Interpolation avec ${} :

Fenêtre de terminal
nix eval --expr 'let name = "Nix"; in "Hello ${name}"'
Résultat
"Hello Nix"

Pour interpoler un nombre, convertissez avec toString :

let port = 8080; in "Port: ${toString port}"
# "Port: 8080"

Chaînes multilignes — délimitées par '' (deux apostrophes simples) :

''
Première ligne
Deuxième ligne
''

L’indentation minimale commune est automatiquement supprimée.

Fenêtre de terminal
nix eval --expr 'true && false'
Résultat
false
Fenêtre de terminal
nix eval --expr 'true || false'
Résultat
true

Opérateurs de comparaison : ==, !=, <, >, <=, >=.

Fenêtre de terminal
nix eval --expr '3 > 2'
Résultat
true
null # type "null" — absence de valeur
/tmp # type "path" — chemin absolu
./. # type "path" — chemin relatif
TypeExemplesbuiltins.typeOf
Entier42, -1, 0"int"
Flottant3.14, 7.0"float"
Chaîne"hello", ''multi''"string"
Booléentrue, false"bool"
Nullnull"null"
Chemin/tmp, ./file.nix"path"
Fonctionx: x + 1"lambda"

Les listes sont des collections ordonnées, délimitées par des crochets et séparées par des espaces (pas de virgules) :

[ 1 2 3 ]
[ "hello" true 42 ] # types mixtes autorisés
Fenêtre de terminal
nix eval --expr 'builtins.length [ 1 2 3 ]'
Résultat
3
Fenêtre de terminal
nix eval --expr 'builtins.head [ 10 20 30 ]'
Résultat
10
Fenêtre de terminal
nix eval --expr 'builtins.tail [ 10 20 30 ]'
Résultat
[ 20 30 ]
Fenêtre de terminal
nix eval --expr '[ 1 2 ] ++ [ 3 4 ]'
Résultat
[ 1 2 3 4 ]

L’opérateur ++ concatène deux listes. builtins.elem teste l’appartenance :

Fenêtre de terminal
nix eval --expr 'builtins.elem 3 [ 1 2 3 ]'
Résultat
true

L’attribute set est la structure de données fondamentale de Nix — un dictionnaire clé-valeur. Chaque attribut se termine par un ; :

{
name = "Alice";
age = 30;
active = true;
}
Fenêtre de terminal
nix eval --expr '{ a = 1; b = 2; }.a'
Résultat
1

L’opérateur // fusionne deux attrsets. Les clés du côté droit écrasent celles du côté gauche :

Fenêtre de terminal
nix eval --expr '{ a = 1; b = 2; } // { b = 99; c = 3; }'
Résultat
{ a = 1; b = 99; c = 3; }
Fenêtre de terminal
nix eval --expr 'builtins.attrNames { z = 1; a = 2; m = 3; }'
Résultat
[ "a" "m" "z" ]

Les noms sont retournés triés alphabétiquement.

Fenêtre de terminal
nix eval --expr 'builtins.attrValues { a = 10; b = 20; }'
Résultat
[ 10 20 ]
Fenêtre de terminal
nix eval --expr '{ a = 1; } ? a'
Résultat
true
Fenêtre de terminal
nix eval --expr '{ a = 1; } ? b'
Résultat
false

Combiné avec or pour une valeur par défaut :

let config = { port = 8080; };
in config.host or "localhost"
# "localhost"

Un attrset normal ne peut pas référencer ses propres attributs. Avec rec, il le peut :

rec {
name = "hello";
fullName = "${name}-world"; # utilise 'name' du même attrset
}
# { name = "hello"; fullName = "hello-world"; }

En Nix, les fonctions sont des valeurs comme les autres. La syntaxe est argument: corps — le : sépare l’argument du corps.

Fenêtre de terminal
nix eval --expr '(x: x * 2) 5'
Résultat
10

La fonction x: x * 2 prend un argument x et retourne x * 2. On l’appelle en lui passant 5 juste après.

Nix ne supporte qu’un argument par fonction. Pour plusieurs arguments, on chaîne les fonctions :

Fenêtre de terminal
nix eval --expr '(a: b: a + b) 3 4'
Résultat
7

a: b: a + b est en fait a: (b: a + b) — une fonction qui retourne une fonction.

C’est la forme la plus courante dans les fichiers Nix. L’argument est un attrset destructuré :

Fenêtre de terminal
nix eval --expr '({ name, age }: "${name} a ${toString age} ans") { name = "Alice"; age = 30; }'
Résultat
"Alice a 30 ans"
{ name, port ? 8080 }:
"Server ${name} on port ${toString port}"

Si port n’est pas fourni, la valeur 8080 est utilisée.

{ name, port ? 8080, ... }:
"Server ${name}"

Le ... autorise l’appelant à passer des attributs supplémentaires sans erreur.

Le bloc let...in crée des variables locales immuables :

Fenêtre de terminal
nix eval --expr 'let x = 5; y = 3; in x + y'
Résultat
8

Les variables peuvent se référencer mutuellement :

let
base = 10;
double = base * 2;
triple = base * 3;
in
double + triple
# 50

Le mot-clé with importe tous les attributs d’un attrset dans le scope courant :

Fenêtre de terminal
nix eval --expr 'let s = { a = 1; b = 2; }; in with s; a + b'
Résultat
3

Usage typique — éviter de répéter pkgs. :

# Sans with
packages = [ pkgs.git pkgs.vim pkgs.ripgrep ];
# Avec with
packages = with pkgs; [ git vim ripgrep ];

inherit copie des variables du scope englobant dans un attrset :

Fenêtre de terminal
nix eval --expr 'let x = 1; y = 2; in { inherit x y; }'
Résultat
{ x = 1; y = 2; }

inherit x y; est strictement équivalent à x = x; y = y;.

On peut aussi hériter depuis un autre attrset :

let config = { port = 8080; host = "localhost"; };
in { inherit (config) port host; }
# { port = 8080; host = "localhost"; }

En Nix, if est une expression qui retourne toujours une valeur :

Fenêtre de terminal
nix eval --expr 'if 3 > 2 then "oui" else "non"'
Résultat
"oui"

Le else est obligatoire — toute expression doit retourner une valeur.

Usage typique — paquets conditionnels :

let
debugMode = true;
debugTools = if debugMode then [ pkgs.htop pkgs.ncdu ] else [];
in
basePackages ++ debugTools

import charge et évalue un fichier .nix :

# Si config.nix contient { port = 8080; host = "localhost"; }
let config = import ./config.nix;
in config.port
# 8080

Si le fichier importé est une fonction, il faut l’appeler avec ses arguments :

# Si tools.nix contient { pkgs }: [ pkgs.git pkgs.vim ]
let tools = import ./tools.nix { inherit pkgs; };
in tools

<nixpkgs> est un chemin spécial résolu via les channels ou le registre. import <nixpkgs> {} retourne l’attrset gigantesque contenant tous les paquets.

Nix fournit des fonctions intégrées dans l’objet builtins. Voici les plus utiles :

Fenêtre de terminal
nix eval --expr 'map (x: x * 2) [ 1 2 3 4 ]'
Résultat
[ 2 4 6 8 ]
Fenêtre de terminal
nix eval --expr 'builtins.filter (x: x > 2) [ 1 2 3 4 5 ]'
Résultat
[ 3 4 5 ]
BuiltinUsageExemple
builtins.lengthTaille d’une listebuiltins.length [ 1 2 3 ]3
builtins.headPremier élémentbuiltins.head [ 10 20 ]10
builtins.tailListe sans le premierbuiltins.tail [ 10 20 30 ][ 20 30 ]
builtins.elemAppartenancebuiltins.elem 3 [ 1 2 3 ]true
builtins.attrNamesClés d’un attrsetbuiltins.attrNames { a = 1; }[ "a" ]
builtins.attrValuesValeurs d’un attrsetbuiltins.attrValues { a = 1; }[ 1 ]
builtins.hasAttrTest d’attributbuiltins.hasAttr "a" { a = 1; }true
toStringConversion en chaînetoString 42"42"
builtins.typeOfType d’une valeurbuiltins.typeOf 42"int"

Voici comment les concepts se combinent dans un fichier shell.nix typique :

shell.nix
{ pkgs ? import <nixpkgs> {} }: # ← fonction à argument nommé + défaut
let # ← variables locales
projectName = "mon-api";
pythonEnv = pkgs.python312.withPackages (ps: [ # ← lambda
ps.requests
ps.flask
]);
debugMode = true;
debugTools = if debugMode # ← conditionnel
then [ pkgs.htop pkgs.ncdu ]
else [];
in
pkgs.mkShell { # ← attrset passé à une fonction
name = "${projectName}-dev"; # ← interpolation
packages = with pkgs; [ # ← with
pythonEnv
git
vim
] ++ debugTools; # ← concaténation de listes
PROJECT_NAME = projectName; # ← variable d'environnement
}

Chaque ligne utilise un concept que vous venez d’apprendre. Les guides suivants approfondissent les environnements de développement et la modularisation de ces fichiers.

ConceptSyntaxeDescription
Types42, "hello", true, null7 types primitifs, tout est expression
Listes[ 1 2 3 ]Collections ordonnées, séparées par des espaces
Attrsets{ clé = valeur; }Dictionnaires, structure fondamentale de Nix
Fonctionsarg: corpsValeurs de première classe, un seul argument
Args nommés{ a, b ? 0 }: ...Destructuration d’attrset avec défauts optionnels
let … inlet x = 1; in xVariables locales immuables
withwith pkgs; [ git ]Importe les attributs dans le scope
inherit{ inherit x; }Raccourci pour x = x
if-then-elseif c then a else bExpression conditionnelle (else obligatoire)
Opérateur //a // bFusion d’attrsets (droite écrase gauche)
Opérateur ++[ 1 ] ++ [ 2 ]Concaténation de listes
importimport ./file.nixCharge et évalue un fichier
Interpolation"Hello ${name}"Insertion d’expressions dans les chaînes
  • Tout est expression en Nix : il n’y a pas d’instructions, chaque élément retourne une valeur.
  • Les attrsets ({ clé = valeur; }) sont la structure universelle — les paquets, les configurations, les modules sont tous des attrsets.
  • Les fonctions sont omniprésentes : chaque fichier .nix est généralement une fonction qui attend pkgs ou d’autres arguments.
  • Les variables sont immuables : une fois définies dans un let, elles ne changent plus.
  • with, inherit et // sont des raccourcis syntaxiques qui rendent le code plus lisible — ils ne sont pas obligatoires mais très courants.
  • Le REPL (nix repl) et nix eval --expr sont vos meilleurs alliés pour expérimenter.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn