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 :
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…).target-familyest absent de la spec ⇒cfg(unix)vautfalsesur Air. (Rust dériveunixdetarget_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ésuppose | Air |
|---|---|
| libc comme socle | refusé — frontière C = unsafe |
| errno in-band | refusé — Result<T, Errno> (ADR-019/ADR-021) |
| sémantique POSIX | honoré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! où 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 :
- 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. - L’absence de
std::os::unixest 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 destdà réécrire, 136 appelslibc::) et redondant (air-socketla fournit déjà en safe). - 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,
stdcesserait de confondre famille et backend — et Air pourrait êtreunix-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
-
Air est un
target_osdistinct. La spec conserveos = "air". Air est, du point de vue du système de cibles Rust, une plateforme — unestdet un contrat d’exécution propres — qui utilise le noyau Linux comme substrat, et non une variante (env/vendor) de la plateformelinux. -
Air est non-POSIX par conception. La spec ne déclare pas
target-family, donccfg(unix) == falseest un invariant normatif d’Air. Aucun code Air ne doit dépendre decfg(unix); tout code spécifique à Air se gate en#[cfg(target_os = "air")]. Réintroduiretarget-family = "unix"(directement, ou indirectement en passantos = "linux") est interdit : cela ramènerait la présomption POSIX et ses trous. -
Nommage — incohérence à corriger. Le triple actuel
x86_64-unknown-linux-airplacelinuxen position OS alors queos = "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) —airen position OS/vendor,nonemarquant l’absence de libc C. Le champllvm-targetreste…-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. -
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) == falsetransforme « Air choisit sa surface » en propriété querustcfait 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
unixdestd. 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.