ADR-090 — Binding de la couche 1 dans std par rustc-dep-of-std : découplage plutôt que fork, sans descellement de la couche 0
Statut : Accepté (2026-07-11, décision BDFL). Met en œuvre ADR-088
(le PAL std custom safe bindant la couche 1). S’appuie sur ADR-054
(scission air-base-core / i18n). Encadre l’usage des exceptions 80 %
ADR-016/ADR-059 (icu4x) et
ADR-034 (RustCrypto) dans la fermeture de std.
Relève de la doctrine dépendances ADR-024 (règle 80 %).
Catégorie : Décision de toolchain (comment le toit std consomme la couche 1). N’altère
aucune API couche 0 scellée ni couche 1 en sémantique ; ce sont des additifs
build-config + une discipline de layering.
Instruit par la mesure (host-first, exploratoire, branche feat/pal-safe-air-target,
incréments 0→3 — voir rt/DECISIONS.md et etude-std-pal-air-safe-fr.md).
Contexte
ADR-088 pose que std repose sur un PAL safe bindant les Managers couche 1. Restait la
question de mécanique : comment un Manager couche 1 (Rust safe) entre-t-il dans le graphe de
dépendances de std ? La reconnaissance (incréments 0→3) a mesuré la réponse :
- Le motif est
rustc-dep-of-std(précédentlibc/hashbrown/hermit-abi) : une crate qui entre dans la fermeture destdest bâtieno_stdavec une featurerustc-dep-of-std(passthrough verscore/compiler_builtins), car elle ne peut pas dépendre destd(elle est ce à partir de quoistdest bâtie — sinon cycle). - Coût côté Air = quasi nul. Binder
air-allocréel (incrément 2) n’a touché que 3Cargo.toml(featurerustc-dep-of-stdsurair-alloc/air-sys-syscall/air-sys-types, +38 lignes) — zéro source.rs, zéro changement sémantique, zéro gate de couverture. Les crates Air visent déjàno_std(Principe 4). Le descellement formel de la couche 0 n’est donc PAS nécessaire. - Le vrai mur est externe. Toute dépendance externe tirée dans la fermeture de
stdqui n’a pasrustc-dep-of-stdamont heurte l’erreur « two cores » (E0463) et impose un fork vendored (le casbitflags, incrément 2). L’audit (incrément 3) a chiffré le binding naïf de tous les Managers à ~71 forks (33 icu4x viaair-base-lib+ 37 RustCrypto viaair-crypto+bitflags) — intenable. - Mais ce fardeau est accidentel, donc évitable. Les deux grappes lourdes n’entrent dans
stdque par couplage de chemin d’import : (a)AirError/AirResult/AirPathsont définis dansair-base-core(icu-free) ;air-base-libne fait que les ré-exporter en y ajoutant icu4x — un Manager qui importe depuisair-base-libtraîne inutilement 33 crates ; (b)AirRandom::filln’appelle quegetrandom(couche 0) — le rattacher à un chemin crypto traîne inutilement 37 crates. Ni icu4x ni RustCrypto ne sont utilisés parstd(agnostique à la locale ; pas de crypto dans le PAL).
Décision
Les Managers couche 1 entrent dans std par rustc-dep-of-std (build-config), et la fermeture
de std est gardée minimale par découplage, jamais par fork des exceptions 80 %.
-
Mécanisme :
rustc-dep-of-std, pas de descellement. Une crate Air dans la fermeture destdreçoit une featurerustc-dep-of-std(build-config) et resteno_std. Aucun changement sémantique d’une API couche 0 scellée ou couche 1. Pas de re-tag de la couche 0. -
Discipline de découplage (absolue) —
air-base-core, jamaisair-base-lib. Tout bras PALsys/<domaine>/air.rs, et tout Manager destiné à la fermeture destd, importe ses types transverses (erreurs, chemins, temps monotone, os_str, log, encodages) depuisair-base-core(icu-free), JAMAISair-base-lib(qui tire icu4x).air-base-libetAirString+icu4x restent le domaine applicatif (couche 2 / apps, consommateurs destd, au-dessus). -
Interdiction des exceptions 80 % lourdes dans la fermeture de
std. Binder un Manager dansstdinterdit de tirer icu4x (ADR-016/059) ou RustCrypto (ADR-034) — ou toute exception 80 % à grande grappe — dans sa fermeture. Ces exceptions vivent au niveau consommateur (couche 2libair, applications), au-dessus destd, oùstd::fs+String+AirString- icu4x cohabitent librement. Elles ne descendent jamais dans la fondation.
-
AirRandom/randomsur un chemin sans crate crypto. Le brassys/random/airest rattaché à un chemin qui n’entraîne pas RustCrypto (isolation — feature minimale surair-crypto, ou micro-surface dédiée).AirRandom::fillne fait qu’appelergetrandom. -
Frontière
no_std= relation àstd, pas un numéro de couche. Composant à partir duquelstdest bâtie (couche 0, couche 1, backend PAL) ⇒no_stdpour toujours (dépendance destd, ne peut en dépendre). Consommateur destd(couche 2libair, applications) ⇒stdcomplet.String/Vec(types d’alloc, =std::String) sont disponibles dès la couche 1 ;AirString(i18n) est un type applicatif, jamais requis par le PAL. -
Politique de fork des dépendances externes (règle 80 %). Une dépendance externe sans
rustc-dep-of-stdamont, tirée dans la fermeture destd, impose un fork vendored localisé (patch + script, façonbitflags, réversible). On minimise ce nombre : auditer avant de binder, préférer le découplage (déplacer l’import) ou le retrait / sous-module (règle 80 %, ADR-024) au fork. On ne forke PAS icu4x ni RustCrypto (exceptions ratifiées, réimplémentation exclue) — on les garde hors de la fermeture destd. -
Industrialisation par vagues. (a) Vague 0 : binding réel du noyau gratuit (
air-base-core,air-thread/futex+sync,air-handle,air-stdio,air-env, +air-alloc), fermeture propre = 1 seul fork (bitflags). (b) Vague 1 :air-filesystem/air-socket/air-process/air-runtimeaprès redirection d’imports →air-base-core+ additifcurrent_exe. (c) Vague 2 :randomisolé + correction du bug spawn natif aarch64 (thread2 arches). Jalon :hello-stdtourne sur le PAL safe, 2 arches, sanslibair_cliée (objectif ADR-088).
Conséquences
- La couche 0 scellée reste intacte (build-config seul) ; la couche 1 évolue par re-sceau
additif (
current_exe, éventuelReadDirparesseux) — pas de refonte. - Chaque programme Air garde une fondation minimale :
stdne traîne ni icu4x ni RustCrypto (ni leurs tables de données baked-in) — cohérent avec les budgets mémoire (Pi 4) et la fondation safe/auditable d’ADR-088. En static-PIE, unhello worldne paie pas l’Unicode. - Dette de fork bornée à 1 (
bitflags) tant que la discipline de découplage tient ; toute nouvelle dép externe dans la fermeture destdpasse par l’audit (règle 80 %). air-base-libconserve son rôle d’API i18n historique, au niveau consommateur — non déprécié, simplement hors du cheminstd.- Caveat outillage : le motif
rustc-dep-of-stdlaisse des façadesrustc-std-workspace-*dansCargo.lock(jamais compilées feature OFF) — vérifiercargo deny/audit/macheteet la repro bit-pour-bit (ADR-025) avant tout merge surmain. - Lien RFC amont : ce mécanisme downstream (fork
rust-src+rustc-dep-of-std) est ce que l’étage 2 de l’étude RFC (etude-rfc-trait-backend-std-fr.md, backend hors-arbre via-Z build-std-platform) viserait à remplacer côté amont. La preuve downstream nourrit l’RFC.
Alternatives rejetées
- Poser
stdsurair-base-lib(avec icu4x) : rejeté — fait entrer tout icu4x (~33 crates- tables de données) dans la fondation de chaque programme, pour une capacité que
stdn’utilise jamais (agnostique à la locale) ; risque de cycle (defaultd’icu4x tirestd— ADR-054) ; ~33 forksrustc-dep-of-stdà maintenir. Le découplage versair-base-corel’évite à coût nul.
- tables de données) dans la fondation de chaque programme, pour une capacité que
- Forker les 33 icu4x / 37 RustCrypto pour
rustc-dep-of-std: rejeté — ~71 forks + dette de re-sync, dont 37 crypto sécurité-sensibles (re-audit à chaque sync). Intenable et inutile (deux grappes non utilisées parstd). - Descelller/refondre la couche 0 pour la rendre dépendance de
std: rejeté comme inutile — la mesure (incrément 2) montre que seule une feature build-config est requise ; les crates sont déjàno_std. Un descellement sémantique serait un risque gratuit. - Réimplémenter un Unicode/crypto maison pour éviter icu4x/RustCrypto : rejeté — exceptions 80 % ratifiées (ADR-016/059/034) ; hors sujet, car le problème n’est pas la dépendance mais son placement (consommateur, pas fondation).
- Attendre l’RFC amont (backend
std::sysde première classe) avant d’avancer : rejeté comme bloquant — on prouve downstream d’abord, on propose l’RFC ensuite (stratégie ADR-088).