Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-106 — Air est un target_os distinct et non-POSIX sur substrat noyau Linux (et son nommage upstream)

Statut : Accepté (2026-07-25, décision BDFL). RFC de direction (ADR-015). S’appuie sur ADR-088 (std Rust sur PAL safe couche 1, sans libc C), ADR-050 (spec de la cible *-unknown-linux-air), ADR-103 (discipline de build linux-air), ADR-004 (Linux tier-1), ADR-073 (configuration binaire). Compagnon d’[ADR-088] : ADR-088 décide que la std repose sur un PAL safe ; ADR-106 décide ce qu’Air est, du point de vue du système de cibles Rust — un OS distinct, non-POSIX — et comment le déclarer (nom du triple, target_os, cfg(unix)).

Catégorie : Doctrine de plateforme + gouvernance de nommage upstream. N’altère aucune couche scellée ; fixe l’identité de la cible et le discours de soumission au projet Rust.

Contexte

[ADR-088] a fait de la std linux-air la bibliothèque standard cible d’Air : une std Rust portée sur un PAL safe bindant la couche 1 safe, sans libc C, static-pie, zéro glibc. [ADR-050] en a écrit la spec — dont un champ qui, jusqu’ici, n’avait pas été décidé explicitement mais seulement posé :

{ "os": "air", "vendor": "air", "llvm-target": "x86_64-unknown-linux-musl", … }

Deux faits en découlent, et n’avaient jamais été tranchés par écrit :

  1. os = "air" — Air se déclare comme un système d’exploitation à part entière au sens du système de cibles Rust, alors même que le noyau est Linux (llvm-target = …-linux-musl, io_uring, futex, clone3, Landlock…).
  2. target-family est absent de la spec ⇒ cfg(unix) vaut false sur Air. (Rust dérive unix de target_family, pas du noyau.)

Question BDFL (2026-07-25). « L’os est “linux” avec une variante “air”, comme on pourrait avoir “linux” variante gnome/kde. Air n’est pas forcément un userland 100 % compatible unix/posix : la norme POSIX est trop floue, sujette à interprétation, et cette latitude laisse des trous de sécurité. Air ne fournit que les concepts du kernel Linux avec une pile userland Rust robuste et sûre — par exemple, pas de fichiers texte sous /etc : un fonctionnement équivalent, mais nos choix. Un peu comme macOS. »

Cette intention — un userland Rust délibérément non-POSIX sur le kernel Linux — est juste et fondatrice. Mais, traduite dans le vocabulaire des cibles Rust, elle mène à la conclusion opposée au modèle « linux + variante », et c’est ce que cet ADR consigne.

Ce que target_env désigne réellement

Dans une cible Rust, target_env (gnu, musl, none…) désigne l’environnement d’exécution bas niveau : la libc et l’ABI userland. Il ne désigne pas une couche applicative — GNOME/KDE/GTK sont des bureaux, hors du triple. L’analogie correcte, si Air était une simple variante, serait donc « un troisième env Linux, à côté de gnu et musl, mais sans libc C ». C’est tentant. C’est aussi ce qu’il faut refuser, pour la raison suivante.

Le nœud : os = "linux" réintroduit la promesse POSIX qu’Air refuse

os = "linux" force target-family = "unix", donc cfg(unix) == true. Or cfg(unix) est un contrat dans tout l’écosystème Rust : il promet la sémantique POSIX. Sous ce contrat, une crate tierce peut légitimement écrire :

#![allow(unused)]
fn main() {
#[cfg(unix)]
fn load_users() { /* getpwnam, lecture de /etc/passwd, sémantique des signaux… */ }
}

Ce code compilerait pour Air et casserait au runtime — silencieusement — parce qu’Air ne fournit pas /etc en texte ([ADR-073]) ni la surface POSIX supposée. C’est exactement le « trou » que la charte veut fermer. Le contrat cfg(unix) est précisément la latitude d’interprétation dénoncée dans la question BDFL, encodée dans un cfg.

À l’inverse, avec os = "air" et pas de target-family, cfg(unix) == false : le bloc ci-dessus ne se compile pas pour Air. Tout portage vers Air devient explicite — un #[cfg(target_os = "air")] que quelqu’un a dû écrire et relire. Nul code ne peut supposer POSIX. C’est la traduction fidèle, vérifiable par le compilateur, de « nos choix, pas ceux hérités de POSIX » — et le prolongement direct de la doctrine « exécution par provenance » du projet.

Le parallèle macOS pointe vers un OS distinct, pas vers une variante

L’analogie macOS est juste — et elle soutient os = "air". macOS tourne sur le noyau XNU/Darwin ; pourtant personne n’écrit os = "darwin", env = "macos". Apple a un target_os à part entière (macos), assumé comme plateforme ; le noyau sous-jacent n’entre pas dans le triple. Air se trouve utiliser le kernel Linux comme substrat, exactement comme macOS utilise XNU : cela ne fait pas d’Air « une variante de Linux » plus que macOS n’est « une variante de BSD ».

(Nuance qui renforce la décision : macOS, lui, honore POSIX — cfg(unix) y est vrai à raison, il est même certifié UNIX. Air fait le choix inverse et l’assume. Raison de plus pour un target_os distinct : Air ne veut surtout pas hériter du contrat unix qu’il ne tiendra pas.)

Amendement du 2026-08-29 — le motif était mal nommé : Air refuse le C, pas Unix

Décision BDFL. Cet ADR justifie cfg(unix) == false par un refus du contrat POSIX. Ce n’est pas la raison réelle, et le dire ainsi a déjà induit un lecteur en erreur. La décision ci-dessous ne change pas — elle est même mieux fondée. Ce qui change est son motif, et ce que l’on peut légitimement en conclure.

Ce qu’Air refuse

La libc C, dont la frontière — extern "C", #[no_mangle], FFI, errno in-band — est unsafe par construction dès qu’on l’appelle depuis Rust. [ADR-088] est sans ambiguïté : l’option « std sur libair_c » a été abandonnée alors qu’elle FONCTIONNAIT (jalon M5, deux arches, zéro glibc), au motif qu’« on a retiré de l’unsafe pour le réintroduire par une libc C sous std ». Ce n’est donc pas « on ne peut pas être unix » : c’est « on ne veut pas de C ».

Ce qu’Air ne refuse PAS

D’être Unix. Air est un userland Unix-like sur noyau Linux — ADR-004 pose le kernel en tier-1, la doctrine dit « le kernel est la bible », et la couche 1 rend fidèlement les idiomes Unix (descripteurs, uid/gid, signaux, sockets domaine-Unix, SCM_RIGHTS, SO_PEERCRED). air-socket fournit AirUnixStream, AirUnixListener, AirUnixDatagram, AirUnixSeqpacket — la famille complète, en Rust safe.

Le nœud réel : cfg(unix) confond trois choses

Ce que cfg(unix) présupposeAir
libc comme soclerefusé — frontière C = unsafe
errno in-bandrefuséResult<T, Errno> (ADR-019/ADR-021)
sémantique POSIXhonorée, largement

Air refuse les deux premières et honore la troisième. rustc n’offre aucun moyen de dire « famille unix, backend sans C » : sys/pal/mod.rs est un cfg_select!unix est la première branche, testée avant tout target_os, et elle mène à pal/unix — écrit en libc::. Poser target-family = "unix" ramène mécaniquement le C, et rendrait le bras target_os = "air" inatteignable.

cfg(unix) == false est un CONTOURNEMENT d’une limite amont, non une déclaration sur la nature d’Air. La note d’étude du projet le dit déjà, et mieux que le corps de cet ADR : « unix n’est pas un OS, c’est une famille qui présuppose libc + errno in-band + sémantique POSIX. Air est Linux mais hors unix-family (pas de libc, safe) » (etude-rfc-trait-backend-std-fr.md).

L’argument /etc/passwd est retiré

Le corps de cet ADR illustre le « contrat POSIX » par une crate tierce qui lirait /etc/passwd en texte. L’exemple ne tient pas : cfg(unix) ne promet aucun /etc/passwd — Android est unix et n’en a pas de conventionnel. Ce que l’exemple décrit réellement est l’absence de libc (getpwnam n’existe pas chez nous), habillée en argument sémantique. Il est retiré comme justification ; le fait qu’il énonce ([ADR-073] : pas de /etc en texte) reste vrai par ailleurs.

Ce qui change dans la lecture, et ce qui ne change pas

Ne change pas — les quatre points de la Décision, à l’identique. Le mécanisme qui les impose (cfg_select!pal/unix → libc) est intact, et il découle du refus du C seul.

Change :

  1. La formule « Air est non-POSIX par conception » (Décision 2) doit se lire « Air est hors unix-family par conception, parce que cette famille présuppose la libc C ». La non-POSITÉ est une conséquence du contournement, pas un objectif.
  2. L’absence de std::os::unix est un DOMMAGE COLLATÉRAL, non une propriété recherchée. Rien n’interdit, sur le fond, qu’Air expose une surface domaine-Unix ; c’est aujourd’hui coûteux (≈ 3 500 lignes de std à réécrire, 136 appels libc::) et redondant (air-socket la fournit déjà en safe).
  3. Un horizon s’ouvre, qui était fermé par l’ancien motif. Si le trait de backend OS-neutre proposé par la note d’étude aboutit en amont, std cesserait de confondre famille et backend — et Air pourrait être unix-family et sans C. Sous l’ancien motif, ce serait une reddition ; sous celui-ci, c’est l’issue souhaitable.

Ce que l’amendement n’autorise pas

Réintroduire target-family = "unix" reste interdit tant que la limite amont existe. L’interdit est inchangé ; seule sa raison l’est. Et l’invariant « aucun code Air ne doit dépendre de cfg(unix) ; tout code spécifique se gate en #[cfg(target_os = "air")] » tient sans réserve.

Décision

  1. Air est un target_os distinct. La spec conserve os = "air". Air est, du point de vue du système de cibles Rust, une plateforme — une std et un contrat d’exécution propres — qui utilise le noyau Linux comme substrat, et non une variante (env/vendor) de la plateforme linux.

  2. Air est non-POSIX par conception. La spec ne déclare pas target-family, donc cfg(unix) == false est un invariant normatif d’Air. Aucun code Air ne doit dépendre de cfg(unix) ; tout code spécifique à Air se gate en #[cfg(target_os = "air")]. Réintroduire target-family = "unix" (directement, ou indirectement en passant os = "linux") est interdit : cela ramènerait la présomption POSIX et ses trous.

  3. Nommage — incohérence à corriger. Le triple actuel x86_64-unknown-linux-air place linux en position OS alors que os = "air". C’est une incohérence interne (le nom dit une chose, la spec en dit une autre) qu’upstream relèverait immédiatement. Décision : la cible sera renommée pour que le nom concorde avec la spec. Forme retenue : <arch>-air-none (ex. x86_64-air-none, aarch64-air-none) — air en position OS/vendor, none marquant l’absence de libc C. Le champ llvm-target reste …-unknown-linux-musl (c’est le substrat de génération de code LLVM, pas l’identité Air). Le renommage est planifié, pas immédiat : il touche [ADR-050], rt/targets/*.json, la CI et [ADR-103] (§ discipline de build) et sera conduit par un incrément dédié piloté par grep (zéro référence cassée), sous RFC d’amendement de ces ADR.

  4. Discours de soumission upstream. Toute soumission au projet Rust (voir Conséquences) présentera Air comme « une plateforme sur substrat noyau Linux », avec l’argumentaire ci-dessus (non-POSIX assumé, cfg(unix) == false, parallèle macOS/XNU), et jamais comme « une variante de Linux ». Le nom soumis suivra la décision 3.

Conséquences

Positives.

  • La non-POSITÉ devient vérifiable par le compilateur, pas seulement documentée : cfg(unix) == false transforme « Air choisit sa surface » en propriété que rustc fait respecter à chaque crate. C’est l’incarnation la plus forte de la charte.
  • Alignement avec la doctrine [ADR-073] (pas de /etc texte) et « exécution par provenance » : aucun code ne peut supposer un environnement POSIX absent.
  • Discours upstream cohérent : le nom, l’os, la famille et le récit disent tous la même chose — condition d’un accueil sérieux d’un MCP tier-3.

Négatives / coûts assumés.

  • Pas de réutilisation gratuite du bras unix de std. Chaque famille du PAL doit être bindée explicitement à la couche 1 (c’est déjà le cas, cf. [ADR-088]/[ADR-090]). Assumé : c’est le prix de l’absence de trous.
  • Portage explicite de chaque crate tierce vers target_os = "air". Assumé et voulu : une crate qui ne compile pas pour Air est une crate dont personne n’a validé le comportement sur Air.
  • Coût de renommage (décision 3) : migration rt/, CI, ADR-050/103. Planifié, non urgent.

Procédure upstream (référence, hors décision). La déclaration d’une nouvelle cible au projet Rust suit la Target Tier Policy (doc.rust-lang.org/rustc/target-tier-policy.html) et le rustc-dev-guide, « Adding a new target ». Route réaliste : tier 3 — MCP auprès de la compiler team, spec in-tree sous compiler/rustc_target/src/spec/targets/, backend std sous library/std/src/sys/pal/air/, fiche platform-support/<nom>.md avec un target maintainer nommé. Redox est le modèle mécanique (tier 3, PAL dédié, libc maison relibc, -Z build-std) — mais Redox est un OS réellement distinct (microkernel), tandis qu’Air partage le noyau Linux : d’où l’importance du discours « plateforme sur substrat » (décision 4). Vecteur upstream plus ambitieux, en second temps : la note interne docs/notes/etude-rfc-trait-backend-std-fr.md (formaliser la frontière std::sys en trait de backend OS-neutre), qui relèverait d’une RFC et non d’un simple MCP tier-3. Ces démarches ne sont pas engagées par cet ADR ; il en fixe les prérequis de fond (identité, non-POSITÉ, nommage).

Alternatives rejetées

Aucune alternative n’a été consignée lors de l’instruction.