ADR-054 — Scission d’air-base-lib : un cœur air-base-core sans i18n pour la fermeture runtime
Statut : Accepté (2026-06-30, ratifié par le BDFL). RFC de correction d’architecture
(ADR-015). Companion d’ADR-052
(layering air-runtime couche 1) et d’ADR-050 (cible
*-linux-air). Précise ADR-053 (la libc s’appuie sur icu4x) en
traçant la frontière de compilation d’icu4x. Rectifie une affirmation erronée de l’en-tête
d’air-base-lib (« icu4x … aucune feature std »).
Catégorie : Architecture (layering, fondateur). Toute évolution structurante = RFC.
Contexte
Le constat empirique (sonde de build, étape 6)
L’étape 6 du programme *-linux-air consiste à recâbler rt/ sur l’objet couche 1 air-runtime
(ADR-052), donc à builder la fermeture d’air-runtime pour *-linux-air (-Z build-std
core+alloc, no_std, nightly). Une sonde (2026-06-30, speedy, x86) a tranché empiriquement la
faisabilité longtemps redoutée (couplage air-base-lib → icu4x) :
error[E0463]: can't find crate for `std`
--> icu_provider-2.2.0/src/lib.rs:198:5
|
198| extern crate std;
= note: the `x86_64-unknown-linux-air` target may not support the standard library
cargo tree -e features donne la chaîne exacte :
air-memory / air-thread / air-process
→ air-base-lib (feature "default")
→ icu_calendar (feature "default")
→ std
Deux faits en découlent :
- Ce n’est pas
libm. Le build meurt sur la featurestdd’icu4x avant d’atteindre la moindre crate à math flottante (calendrical_calculations).libmest donc hors du chemin critique de l’étape 6 ; il ne concerne que le futur port i18n de la libc (ADR-053). - La cause est un couplage de layering :
air-memory/air-thread/air-processne consomment d’air-base-libqu’AirError(et, pour deux d’entre eux, l’horloge monotone etAirPath) — mais en dépendant d’air-base-lib, ils compilent tout l’i18n (icu_normalizer/casemap/segmenter/ collator/calendar). La fondation runtime traîne la pile Unicode entière pour un type d’erreur.
La rectification d’un postulat erroné
L’en-tête d’air-base-lib/src/lib.rs affirme : « icu4x est no_std + alloc par conception
(données compiled_data baked-in, aucune feature std) ». C’est faux : les deps icu du
workspace (Cargo.toml, [workspace.dependencies]) n’ont pas default-features = false, donc la
feature default d’icu — qui inclut std — est active. Cela passait inaperçu car air-base-lib
n’a jamais été compilé que pour des cibles *-linux-gnu où std existe (l’extern crate std d’icu
résolvait sans bruit). La cible no_std *-linux-air révèle le postulat.
Cartographie du couplage icu (mesurée)
Module air-base-lib | icu ? | Consommé par le runtime ? |
|---|---|---|
error, id, path, encoding, log | 0 | error (partout), path (air-process) |
time — AirInstant/AirDuration (monotone) | 0 | air-thread, air-process |
time — AirDateTime/AirCalendar (calendaire) | icu_calendar | non |
string — AirString/AirLocale | icu_* | air-process (API Command) |
Le « cœur sans-icu » (error/id/path/time-monotone/encoding/log) est nettement séparable de
l’i18n (string + calendaire). Seul accroc : air-process::Command prend arg(&AirString) /
env(&AirString, &AirString), ce qui tire AirString (icu).
Décision
D1 — Créer air-base-core (couche 1, no_std, zéro icu)
Extraire d’air-base-lib un cœur sans i18n dans une nouvelle crate crates/air-base-core :
error ([AirError], [AirErrorKind], [AirResult], ResultContextExt, absorption d’Errno),
id, path ([AirPath], octets), time monotone ([AirInstant], [AirDuration]),
encoding (base64/hex), log ([AirLog]). Aucune dépendance icu ; core + alloc + couche 0
(air-sys-types ; air-sys-syscall uniquement pour l’horloge monotone, comme aujourd’hui).
D2 — air-base-lib conserve l’i18n et ré-exporte le cœur (compatibilité)
air-base-lib garde les types i18n : string ([AirString]/[AirLocale] + segmentation/
normalisation), et la partie calendaire de time ([AirDateTime]/[AirCalendar]). Elle dépend
de air-base-core + icu4x, et ré-exporte tout air-base-core (pub use air_base_core::…) de
sorte que air_base_lib::AirError, air_base_lib::AirPath, etc. continuent de fonctionner à
l’identique. Les 7 consommateurs non-runtime (air-config, air-device, air-filesystem,
air-crypto, air-socket, air-config-compile, air-base-capi) ne changent pas ; air-base-capi
(ABI C) accède toujours à AirError via air_base_lib:: → frontière ABI intacte (ADR-045).
D3 — Le runtime bascule sur air-base-core
air-memory, air-thread, air-process dépendent désormais d’air-base-core (et non plus
d’air-base-lib). Leur fermeture ne compile plus aucun code icu ⇒ build *-linux-air
débloqué, sans libm pour l’étape 6.
D4 — air-process::Command : args/env en octets (AirPath), plus AirString
Command::arg/args/env prennent désormais &AirPath (octets) au lieu de &AirString. Ce
n’est pas un simple contournement du couplage icu : c’est plus correct au sens du Principe 3
(« zéro présomption d’encodage »). Sur Unix, execve(2) prend char *const argv[] / envp[] : des
chaînes d’octets terminées NUL, non garanties UTF-8. Représenter args/env en octets est fidèle
à la sémantique kernel (doctrine « kernel = bible »). Changement d’API publique d’air-process,
au rayon d’impact nul (aucun appelant externe d’air-process::Command à ce jour). Les appelants
futurs passeront des octets (AirPath/&[u8]) ; l’i18n ne descend plus dans la couche process.
D5 — time.rs scindé selon la frontière icu
La partie monotone (AirInstant/AirDuration, zéro icu) part dans air-base-core::time ; la
partie calendaire (AirDateTime/AirCalendar, icu_calendar) reste dans air-base-lib::time.
Les deux familles sont déjà indépendantes (horloge monotone vs mur).
D6 — Frontière de compilation d’icu4x : hors périmètre de cet ADR
Rendre icu4x lui-même compilable en no_std pour *-linux-air (default-features = false sur les
deps icu du workspace + sélection des features no_std, puis la question libm) est nécessaire pour
la libc (ADR-053) mais pas pour l’étape 6. Cet ADR déplace ce travail dans le chantier libc/
i18n (futur), où il sera traité avec la décision libm (réécriture vs vendoring, ADR-024). En
attendant, l’en-tête erroné d’air-base-lib est rectifié (le default d’icu tire std ; le
build no_std d’icu reste à faire pour la libc).
Conséquences
- Étape 6 débloquée : la fermeture
air-runtimecompile pour*-linux-airsans icu ni libm (à reprouver par la sonde après le split — confirmation empirique attendue). - Compatibilité : ré-export D2 ⇒ zéro changement pour les 7 consommateurs non-runtime et l’ABI C.
- Architecture plus saine : la fondation système (erreur/temps/chemin/id) ne dépend plus de l’i18n ; tout consommateur qui n’a pas besoin d’Unicode cesse de compiler icu (bénéfice au-delà du runtime — temps de build, surface).
libmclarifié : confirmé hors du chemin de l’étape 6 ; reste un maillon du port icu4x futur (calendrier + LSTM), tranché plus tard (ADR-024).- Couverture :
air-base-coreest couche 1 ⇒ 100 % lignes/branches (Principe 1), commeair-base-libaujourd’hui. Les fichiers déplacés gardent leurs tests (unit + property + doctests + fuzz le cas échéant). - Dette rectifiée : le postulat « icu sans
std» est corrigé dans la doc.
Alternatives rejetées
- Option B — forcer icu en
no_stdau niveau workspace (default-features = false+ features no_std sur les deps icu). Plus large (affecte le build stable de tous les consommateurs), et recule seulement le mur : on retomberait surlibm(jamais atteint par la sonde). Nécessaire un jour pour la libc, mais disproportionné pour débloquer l’étape 6. Déféré (D6). - Isoler le cœur d’
AirString(conteneur UTF-8 sans-icu + extension icu pour normalisation/ segmentation), pour garder l’APICommanden « chaîne ». Refonte d’AirStringplus invasive, et moins correcte que des octets pour args/env (Principe 3). Rejeté au profit de D4. - Déplacer l’i18n hors d’
air-base-lib(renommer le cœur enair-base-lib, l’i18n enair-base-text). Plus « propre » sur les noms, mais casse tous les consommateurs d’AirString(air_base_lib::AirStringdisparaîtrait). Rejeté : D1+D2 (extraire le cœur, ré-exporter) obtient le découplage sans breakage.
Mise en œuvre (esquisse, code headless après ratification)
- Créer
crates/air-base-core; y déplacererror.rs,id.rs,path.rs,encoding.rs,log.rs, et la partie monotone detime.rs. Tests déplacés avec. air-base-lib: retirer ces modules, dépendre d’air-base-core,pub use air_base_core::…(ré-export), garderstring.rs+ partie calendaire detime.rs.air-memory/air-thread/air-process: dépendanceair-base-lib→air-base-core(importsair_base_lib::→air_base_core::).air-process::Command:AirString→AirPath(args/env) ; adapter tests/doctests.- Couverture 100 %
air-base-core+air-base-lib; barrière 2-arches verte ;DEPENDENCIES.md,docs/SUMMARY.md, registre ADR, INDEX à jour ;mdbook buildOK. - Reprouver la sonde : fermeture
air-runtimecompile pour*-linux-air(carbon x86 + raspi/CI).