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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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,inheritetif-then-else - Transformer des données avec les builtins (
map,filter,attrNames…) - Écrire des expressions complètes prêtes pour
shell.nixouflake.nix
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »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.nixouflake.nixde 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 : votre terrain d'expérimentation
Section intitulée « Le REPL : votre terrain d'expérimentation »Le REPL (Read-Eval-Print Loop) est un terminal interactif pour tester des expressions Nix. Lancez-le avec :
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 :
nix eval --expr '1 + 2'3Tous les exemples de ce guide sont testables avec l'une ou l'autre méthode.
Types primitifs
Section intitulée « Types primitifs »Nix possède 7 types de base. Vous pouvez vérifier le type de n'importe quelle
valeur avec builtins.typeOf :
Entiers et flottants
Section intitulée « Entiers et flottants »nix eval --expr '1 + 2'3nix eval --expr '7 / 2'3La division entre entiers est entière (pas d'arrondi, troncature). Pour obtenir un résultat décimal :
nix eval --expr '7.0 / 2.0'3.5nix eval --expr 'builtins.typeOf 42'"int"Chaînes de caractères
Section intitulée « Chaînes de caractères »Les chaînes sont délimitées par des guillemets doubles :
nix eval --expr '"hello" + " world"'"hello world"L'opérateur + concatène les chaînes (et additionne les nombres, Nix
distingue par le type des opérandes).
Interpolation avec ${} :
nix eval --expr 'let name = "Nix"; in "Hello ${name}"'"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.
Booléens
Section intitulée « Booléens »Les booléens servent surtout à piloter des if et des attributs de
configuration NixOS du type services.nginx.enable = true;. Deux propriétés
comptent en pratique : les opérateurs && et || sont paresseux, ils
n'évaluent l'opérande de droite que si le résultat n'est pas déjà décidé, et
Nix n'accepte aucune conversion implicite. Un entier, une chaîne vide ou
null ne valent pas false : une condition doit produire un vrai booléen.
nix eval --expr 'true && false'falsenix eval --expr 'true || false'trueOpérateurs de comparaison : ==, !=, <, >, <=, >=.
nix eval --expr '3 > 2'trueNull et chemins
Section intitulée « Null et chemins »null n'est pas une chaîne vide : c'est la valeur qui signale explicitement
l'absence de valeur, très utilisée dans les options NixOS pour dire « laisse
la valeur par défaut du module ». Le chemin est plus surprenant pour qui
vient d'un autre langage : il s'écrit sans guillemets et Nix le résout dès
l'évaluation. Un chemin doit contenir au moins un /, ce qui explique la forme
./. pour désigner le répertoire courant.
null # type "null" : absence de valeur/tmp # type "path" : chemin absolu./. # type "path" : chemin relatifRécapitulatif des types
Section intitulée « Récapitulatif des types »La troisième colonne donne la chaîne exacte que renvoie builtins.typeOf, celle
que vous comparerez dans vos tests. Deux pièges de nommage : une fonction est de
type "lambda", pas "function", et un attrset est de type "set", pas
"attrset". Ce tableau couvre les types primitifs ; s'y ajoutent les deux
types composés décrits plus bas, les listes ("list") et les attribute sets
("set").
| Type | Exemples | builtins.typeOf |
|---|---|---|
| Entier | 42, -1, 0 | "int" |
| Flottant | 3.14, 7.0 | "float" |
| Chaîne | "hello", ''multi'' | "string" |
| Booléen | true, false | "bool" |
| Null | null | "null" |
| Chemin | /tmp, ./file.nix | "path" |
| Fonction | x: 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ésOpérations sur les listes
Section intitulée « Opérations sur les listes »Nix ne propose ni boucle ni indexation par crochets : écrire liste[1] est lu
comme un appel de fonction et provoque l'erreur
attempt to call something which is not a function. L'accès par position passe
par builtins.elemAt liste 1. Les opérations ci-dessous forment la base de
tout le reste. Elles sont toutes non destructives : builtins.tail ne
modifie rien, elle retourne une nouvelle liste, l'originale reste intacte
puisque les valeurs sont immuables.
nix eval --expr 'builtins.length [ 1 2 3 ]'3nix eval --expr 'builtins.head [ 10 20 30 ]'10nix eval --expr 'builtins.tail [ 10 20 30 ]'[ 20 30 ]nix eval --expr '[ 1 2 ] ++ [ 3 4 ]'[ 1 2 3 4 ]L'opérateur ++ concatène deux listes. builtins.elem teste l'appartenance :
nix eval --expr 'builtins.elem 3 [ 1 2 3 ]'trueAttribute sets (attrsets)
Section intitulée « Attribute sets (attrsets) »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;}Accès et manipulation
Section intitulée « Accès et manipulation »Le point sert à descendre dans un attrset, et il s'enchaîne sur plusieurs
niveaux : config.services.nginx.enable est un simple accès imbriqué. Retenez
que l'accès à un attribut absent ne renvoie pas null mais provoque une
erreur d'évaluation attribute missing, ce qui rend indispensables les deux
mécanismes vus juste après, l'opérateur ? et le mot-clé or.
nix eval --expr '{ a = 1; b = 2; }.a'1Fusion avec //
Section intitulée « Fusion avec // »L'opérateur // fusionne deux attrsets. Les clés du côté droit écrasent celles
du côté gauche :
nix eval --expr '{ a = 1; b = 2; } // { b = 99; c = 3; }'{ a = 1; b = 99; c = 3; }Introspection
Section intitulée « Introspection »Ces deux fonctions permettent de parcourir un attrset dont vous ne connaissez pas
les clés à l'avance, situation courante quand vous explorez nixpkgs ou la
configuration d'un module. Un point important pour la reproductibilité :
attrNames et attrValues renvoient toujours les éléments dans le même
ordre, trié par nom d'attribut, quel que soit l'ordre d'écriture dans le
fichier source.
nix eval --expr 'builtins.attrNames { z = 1; a = 2; m = 3; }'[ "a" "m" "z" ]Les noms sont retournés triés alphabétiquement.
nix eval --expr 'builtins.attrValues { a = 10; b = 20; }'[ 10 20 ]Test d'existence avec ?
Section intitulée « Test d'existence avec ? »L'opérateur ? répond à la question « cet attribut existe-t-il », sans lever
d'erreur et sans évaluer sa valeur. C'est le test à utiliser avant tout accès
optionnel. Attention à ne pas le confondre avec le ? des arguments de
fonction vu plus loin, qui déclare une valeur par défaut : même caractère, deux
significations sans rapport.
nix eval --expr '{ a = 1; } ? a'truenix eval --expr '{ a = 1; } ? b'falseCombiné avec or pour une valeur par défaut :
let config = { port = 8080; };in config.host or "localhost"# "localhost"Attrsets récursifs avec rec
Section intitulée « Attrsets récursifs avec rec »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}# { fullName = "hello-world"; name = "hello"; }Fonctions
Section intitulée « Fonctions »En Nix, les fonctions sont des valeurs comme les autres. La syntaxe est
argument: corps, le : sépare l'argument du corps.
Fonctions simples
Section intitulée « Fonctions simples »nix eval --expr '(x: x * 2) 5'10La fonction x: x * 2 prend un argument x et retourne x * 2. On
l'appelle en lui passant 5 juste après.
Fonctions curryfiées (plusieurs arguments)
Section intitulée « Fonctions curryfiées (plusieurs arguments) »Nix ne supporte qu'un argument par fonction. Pour plusieurs arguments, on chaîne les fonctions :
nix eval --expr '(a: b: a + b) 3 4'7a: b: a + b est en fait a: (b: a + b), une fonction qui retourne une
fonction.
Fonctions à arguments nommés (pattern matching)
Section intitulée « Fonctions à arguments nommés (pattern matching) »C'est la forme la plus courante dans les fichiers Nix. L'argument est un attrset destructuré :
nix eval --expr '({ name, age }: "${name} a ${toString age} ans") { name = "Alice"; age = 30; }'"Alice a 30 ans"Valeurs par défaut avec ?
Section intitulée « Valeurs par défaut avec ? »Une valeur par défaut rend un argument facultatif à l'appel. Elle est évaluée
paresseusement, seulement si l'appelant n'a rien fourni, et peut donc contenir
une expression coûteuse sans pénaliser les autres appels. C'est exactement ce
mécanisme qui permet à un shell.nix de fonctionner avec un simple
nix-shell sans qu'on lui passe le moindre argument.
{ name, port ? 8080 }: "Server ${name} on port ${toString port}"Si port n'est pas fourni, la valeur 8080 est utilisée.
Arguments supplémentaires avec ...
Section intitulée « Arguments supplémentaires avec ... »{ name, port ? 8080, ... }: "Server ${name}"Le ... autorise l'appelant à passer des attributs supplémentaires sans
erreur.
Variables locales : let...in
Section intitulée « Variables locales : let...in »Le bloc let...in crée des variables locales immuables :
nix eval --expr 'let x = 5; y = 3; in x + y'8Les variables peuvent se référencer mutuellement :
let base = 10; double = base * 2; triple = base * 3;in double + triple# 50with, importer un scope
Section intitulée « with, importer un scope »Le mot-clé with importe tous les attributs d'un attrset dans le scope
courant :
nix eval --expr 'let s = { a = 1; b = 2; }; in with s; a + b'3Usage typique, éviter de répéter pkgs. :
# Sans withpackages = [ pkgs.git pkgs.vim pkgs.ripgrep ];
# Avec withpackages = with pkgs; [ git vim ripgrep ];inherit, raccourci d'assignation
Section intitulée « inherit, raccourci d'assignation »inherit copie des variables du scope englobant dans un attrset :
nix eval --expr 'let x = 1; y = 2; in { inherit x y; }'{ 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"; }Conditionnels : if-then-else
Section intitulée « Conditionnels : if-then-else »En Nix, if est une expression qui retourne toujours une valeur :
nix eval --expr 'if 3 > 2 then "oui" else "non"'"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 ++ debugToolsimport, charger un fichier Nix
Section intitulée « import, charger un fichier Nix »import charge et évalue un fichier .nix :
# Si config.nix contient { port = 8080; host = "localhost"; }let config = import ./config.nix;in config.port# 8080Si 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.
Builtins : fonctions intégrées
Section intitulée « Builtins : fonctions intégrées »Nix fournit des fonctions intégrées dans l'objet builtins. Voici les plus
utiles :
map, transformer une liste
Section intitulée « map, transformer une liste »map applique une fonction à chaque élément et retourne une nouvelle liste
de même longueur. Notez qu'elle s'utilise sans préfixe builtins. : quelques
fonctions comme map, toString et import sont exposées directement dans le
scope global, alors que la majorité des autres exigent le préfixe complet.
nix eval --expr 'map (x: x * 2) [ 1 2 3 4 ]'[ 2 4 6 8 ]builtins.filter, filtrer une liste
Section intitulée « builtins.filter, filtrer une liste »filter conserve les éléments pour lesquels la fonction passée en premier
argument retourne true. Cette fonction, appelée prédicat, doit renvoyer un
booléen strict : retourner un entier ou null déclenche une erreur
d'évaluation. En pratique, filter sert surtout à construire des listes de
paquets conditionnelles selon la plateforme ou l'environnement.
nix eval --expr 'builtins.filter (x: x > 2) [ 1 2 3 4 5 ]'[ 3 4 5 ]Autres builtins utiles
Section intitulée « Autres builtins utiles »Ce tableau tient lieu d'aide-mémoire pour les fonctions que vous croiserez dès
vos premiers fichiers. Deux lignes méritent une attention particulière :
toString s'écrit sans le préfixe builtins., contrairement à ses voisines,
et builtins.hasAttr "a" attrset fait exactement le même travail que l'opérateur
attrset ? a, la forme fonctionnelle étant utile quand le nom de l'attribut est
lui-même calculé.
| Builtin | Usage | Exemple |
|---|---|---|
builtins.length | Taille d'une liste | builtins.length [ 1 2 3 ] → 3 |
builtins.head | Premier élément | builtins.head [ 10 20 ] → 10 |
builtins.tail | Liste sans le premier | builtins.tail [ 10 20 30 ] → [ 20 30 ] |
builtins.elem | Appartenance | builtins.elem 3 [ 1 2 3 ] → true |
builtins.attrNames | Clés d'un attrset | builtins.attrNames { a = 1; } → [ "a" ] |
builtins.attrValues | Valeurs d'un attrset | builtins.attrValues { a = 1; } → [ 1 ] |
builtins.hasAttr | Test d'attribut | builtins.hasAttr "a" { a = 1; } → true |
toString | Conversion en chaîne | toString 42 → "42" |
builtins.typeOf | Type d'une valeur | builtins.typeOf 42 → "int" |
Mettre tout ensemble : anatomie d'un shell.nix
Section intitulée « Mettre tout ensemble : anatomie d'un shell.nix »Voici comment les concepts se combinent dans un fichier shell.nix typique :
{ 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 [];inpkgs.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. Le seul élément
nouveau est pkgs.mkShell, une fonction fournie par nixpkgs qui construit un
environnement de développement à partir de l'attrset que vous lui passez. Les
guides sur les environnements de développement et sur la factorisation,
listés en bas de page, détaillent ce que cet attrset accepte.
Récapitulatif des concepts
Section intitulée « Récapitulatif des concepts »Gardez ce tableau sous la main pendant vos premières lectures de fichiers
nixpkgs : la colonne Syntaxe est celle que vous reconnaîtrez à l'écran, la
colonne Description vous dit ce qu'elle fait. Les quatre dernières lignes
sont les plus rentables à mémoriser, car //, ++, import et
l'interpolation apparaissent dans presque toutes les dérivations.
| Concept | Syntaxe | Description |
|---|---|---|
| Types | 42, "hello", true, null | 7 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 |
| Fonctions | arg: corps | Valeurs de première classe, un seul argument |
| Args nommés | { a, b ? 0 }: ... | Destructuration d'attrset avec défauts optionnels |
| let ... in | let x = 1; in x | Variables locales immuables |
| with | with pkgs; [ git ] | Importe les attributs dans le scope |
| inherit | { inherit x; } | Raccourci pour x = x |
| if-then-else | if c then a else b | Expression conditionnelle (else obligatoire) |
| Opérateur // | a // b | Fusion d'attrsets (droite écrase gauche) |
| Opérateur ++ | [ 1 ] ++ [ 2 ] | Concaténation de listes |
| import | import ./file.nix | Charge et évalue un fichier |
| Interpolation | "Hello ${name}" | Insertion d'expressions dans les chaînes |
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »- 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
.nixest généralement une fonction qui attendpkgsou d'autres arguments. - Les variables sont immuables : une fois définies dans un
let, elles ne changent plus. with,inheritet//sont des raccourcis syntaxiques qui rendent le code plus lisible, ils ne sont pas obligatoires mais très courants.- Le REPL (
nix repl) etnix eval --exprsont vos meilleurs alliés pour expérimenter.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Environnements de développement : Appliquez le langage Nix pour créer des shells reproductibles par projet.
- Import, factorisation et pinning : Modularisez vos fichiers Nix et figez les versions pour la reproductibilité.
- Les flakes : Le workflow moderne avec verrouillage automatique des dépendances.