Audit icu4x — vers une réimplémentation Rust pur no_std sur Air
Note de travail (2026-06-30). Objectif : déterminer ce qui manque à Air pour
réécrire icu4x en Rust pur no_std sur types/syscalls/composants Air uniquement.
Base : version pinnée du projet (icu@2.2.0, calendar 2.2.1). Audit mené sur un
clone unicode-org/icu4x (tag icu@2.2.0) sur carbon. Périmètre : les 6 composants
pinnés + leur socle, runtime et datagen (frontière tracée).
0. Pourquoi cet audit
air-base-lib (couche 1) consomme icu4x → toute crate couche 1 qui dépend d’air-base-lib
(air-memory/air-thread/air-process, via AirError) tire icu4x transitivement. Cela a
soulevé, au moment de builder air-runtime pour *-linux-air, la question : icu4x est-il
compatible no_std/cible custom, et que faudrait-il pour en avoir une version maîtrisée full-Rust ?
1. Périmètre
6 composants pinnés ([workspace.dependencies] du projet) :
icu_normalizer, icu_casemap, icu_segmenter, icu_collator, icu_locale_core (2.2.0),
icu_calendar (2.2.1) — tous en features = ["compiled_data", …] (données baked-in).
Socle commun : zerovec, yoke, writeable, potential_utf, tinystr, litemap, et les
crates d’infrastructure icu : icu_provider, icu_collections, icu_properties, icu_locale,
calendrical_calculations.
2. Découverte centrale — le runtime icu4x est DÉJÀ no_std et de calcul pur
- Les 10 crates auditées sont
#![cfg_attr(not(any(test, doc)), no_std)]:no_stden production,stdseulement pour tests/doc. icu4x est no_std-natif par conception. - Le runtime (consommation des tables baked) est du CALCUL PUR : aucune occurrence de
std::{thread,fs,net,time,io,process},SystemTime,Instant::now,File,getrandom,libc::dans le code de production des 6 composants. (Seules traces : des commentaires décrivant un trait « no-std version ofstd::io::Write».) - Besoins système du runtime =
core+alloc. Rien d’autre. Zéro syscall, zéro horloge, zéro thread, zéro fichier. ⇒ Air n’a besoin de fournir aucun composant système (thread/fichier/horloge) au runtime icu4x. Air a déjàcore+alloc.
Conséquence directe sur l’étape 6 / le « blocage » initial : la crainte qu’icu4x empêche le
build *-linux-air est largement dissoute. icu4x étant no_std-pur-compute, il devrait
compiler pour *-linux-air (core+alloc) modulo libm (cf. §5). Le « bloat binaire » est
probablement moot (code mort éliminé par LTO si air-runtime n’utilise qu’AirError).
3. Le SEUL primitif système manquant — math flottante no_std (libm)
core_maths(façade no_std surlibm) est utilisé à deux endroits du périmètre :icu_segmenter/src/complex/lstm/matrix.rs— math du modèle LSTM (segmentation par IA pour thaï/khmer/lao/birman ; activé par la featureauto/lstm, que le projet active).calendrical_calculations(dép deicu_calendar) — math calendaire (astronomie des calendriers lunaires/solaires). Non contournable : le calendrier en a besoin.
- C’est le vrai manque systémique d’Air : pas de
libmno_std(exclu de la v1, ADR-046). Décision requise (cf. §8).
4. Inventaire par composant (runtime, default = compiled_data)
| Composant | Deps icu/socle | Deps externes (hors std/icu) | Notes |
|---|---|---|---|
icu_normalizer | icu_collections, icu_properties, icu_provider, zerovec, + icu_normalizer_data | smallvec, utf8_iter, utf16_iter, write16 ; harfbuzz-traits (optionnel) | NFC/NFD/NFKC/NFKD |
icu_casemap | icu_collections, icu_properties, icu_provider, icu_locale_core, potential_utf, writeable, zerovec, + icu_casemap_data | — (rien hors socle) | upper/lower/title/fold |
icu_segmenter | icu_collections, icu_locale, icu_provider, potential_utf, zerovec, + icu_segmenter_data | core_maths→libm (LSTM), utf8_iter | grapheme/word/line/sentence ; LSTM=floats |
icu_collator | icu_collections, icu_locale, icu_locale_core, icu_normalizer, icu_properties, icu_provider, zerovec, + icu_collator_data | smallvec, utf8_iter, utf16_iter | tri Unicode (UCA) |
icu_locale_core | zerovec, writeable, tinystr, litemap | displaydoc (proc-macro host) | identifiants de locale (BCP-47) |
icu_calendar | icu_locale, icu_locale_core, icu_provider, zerovec, tinystr, calendrical_calculations, + icu_calendar_data | calendrical_calculations→core_maths→libm ; ixdtf (optionnel) ; chrono/jiff/time (interop, optionnels) | calendriers (grégorien, etc.) |
Frontière nette : serde, databake, datagen = features non runtime (génération de
données / chargement non-baked). chrono/jiff/time/ixdtf/harfbuzz-traits = optionnels
(interop / parsing) — non requis pour le cœur.
5. Le socle icu (à réécrire aussi — tout no_std, calcul pur)
icu_provider (mécanique de fourniture de données : clés, requêtes, baked) · icu_collections
(structures Unicode : code-point tries, inversion lists) · icu_properties (propriétés de
caractères Unicode) · icu_locale (négociation de locale) · zerovec/yoke/writeable/
potential_utf (zéro-copie, vues empruntées sur les tables baked) · tinystr/litemap
(petites chaînes ASCII / petites maps) · calendrical_calculations (math calendaire, →libm).
C’est le gros du travail de réécriture — mais mécanique (no_std, calcul pur, zéro syscall).
6. Données / tables — format et provenance
- Chaque
icu_*_dataest une crate#![no_std]quiinclude!unmod.rsgénéré = données Rustconstbaked (viadatabake) compilées dans le binaire (+stubdatavide pour fournir les données autrement, + supportICU4X_DATA_DIRpour données custom). - Provenance (en-tête généré) : CLDR 48.2.0 + ICU 78.1 (icuexport) + LSTM v0.1.0.
- ⇒ Les « tables » du modèle BDFL = données const Rust baked, dérivées des sources Unicode/CLDR. Posséder ce format (ou un équivalent Air) + savoir les régénérer = un chantier en soi.
7. Datagen / outils — host, indépendants de no_std-Air
provider/source(icu_provider_source: « data provider based on CLDR and ICU data ») lit CLDR + export ICU (UCD) ;provider/exportbake en Rust const (databake) ou blobs.- Ce sont des outils de build
host(std libre), indépendants d’une lib/pal Air — commecbindgen/capnpc/le tool C++ capnp. Leno_std-Air ne les contraint pas (décision BDFL). - Dépendance amont inévitable : les sources CLDR + Unicode UCD (et modèles LSTM). Toute réimplémentation doit soit réutiliser le datagen icu4x pour produire nos tables, soit réimplémenter un datagen (std host) consommant les mêmes sources.
8. SYNTHÈSE — ce qui manque à Air (et décisions)
Runtime (consommateur de tables) :
- Rien côté système :
core+allocsuffisent (Air les a). Aucun composant Air (thread/fichier/horloge) requis. ✅ libmno_std= LE manque systémique (calendrical_calculations + LSTM segmenter). Décision : (a) fournir unlibmAir (réécrit ou vendoré, no_std) ; ou (b) restreindre le périmètre (sans LSTM → segmentation par dictionnaire seul ; mais le calendrier garde besoin de libm → (b) ne suffit pas seul). → Air aura besoin d’unlibmno_std.- Petites crates utilitaires
no_stdà vendorer/réécrire (règle 80 %, ADR-024) :smallvec,tinystr,litemap,utf8_iter,utf16_iter,write16. Petites, no_std, zéro syscall.displaydoc= proc-macro host (ou remplacer parDisplaymanuel). - Socle icu (
provider/collections/properties/locale+zerovec/yoke/writeable/potential_utf/calendrical_calculations) : à réécrire (gros mais mécanique, no_std pur).
Données / outils : 5. Format de tables : adopter le baked-const Rust (databake-like) ou un équivalent Air. 6. Datagen (host, std libre) : réutiliser celui d’icu4x ou le réimplémenter ; dans les deux cas, dépendance aux sources CLDR + Unicode UCD (amont, non maîtrisable — c’est la donnée de référence mondiale). Outils produits indépendamment d’une lib/pal Air.
Ce qu’Air N’A PAS, en une ligne : un libm no_std, une poignée de petites crates
utilitaires no_std (vendorables), et le pipeline de données (format de tables + datagen
host sur sources CLDR/UCD). Le reste (runtime no_std, socle calcul-pur) est du portage Rust
sans dépendance système.
9. Implication pour la décision A/B/C (couplage air-base-lib→icu4x, étape 6)
L’audit réduit fortement le risque qui motivait (A) : icu4x est no_std-pur-compute, donc le
build *-linux-air d’air-runtime (qui ne fait que tirer AirError, code icu4x mort →
éliminé) devrait passer modulo libm. Reste à vérifier empiriquement (build-std de la
fermeture pour *-linux-air) — non fait, sur décision « on prend le temps ». La scission
air-base-lib (A) reste un nettoyage d’archi sain (erreur transverse vs i18n lourde), mais
probablement pas un bloqueur de l’étape 6.
✅ VÉRIFIÉ EMPIRIQUEMENT (2026-07-01, ADR-059). Sonde jetable (carbon) : les 6 composants (dont
icu_segmenteravec LSTMauto) + tout le socle +calendrical_calculations/core_maths/libmCOMPILENT pourx86_64-unknown-linux-air(build-std=[core,alloc],--release,EXIT=0, 33 s). Deux conditions triviales : (1)default-features = falsesur lesicu_*(évite la featurestdpar défaut = le « mur std ») ; (2) build--release(le seulextern crate stdrestant, dansicu_provider, est un repli loggingeprintlndebug_assertions-only — inactif en release). ⇒ la réimplémentation du socle est INUTILE ; décision Option V (ADR-059) : Air utilise icu4x, possède l’APIair-string+ les données. Le libm i18n vient du cratelibmamont (viacore_maths), pas deair-libm.
10. Questions ouvertes (décisions BDFL à venir)
libm: réécrire unlibmAir no_std, ou vendorerlibm(exception 80 % nommée) ? Quand (avant l’i18n, car bloquant pour calendar + LSTM) ?- Périmètre LSTM : garder la segmentation LSTM (besoin floats + données modèle) ou dictionnaire seul pour la v1 ?
- Datagen : réutiliser le datagen icu4x pour produire des tables au format Air, ou réimplémenter ? (Réutiliser = pragmatique pour démarrer.)
- Vendoring vs réécriture des petites crates utilitaires (smallvec/tinystr/…).
- Quand : cet effort (i18n Air) est gros ; il se situe APRÈS le runtime/libc, ou en parallèle ?
Méthode : clone icu@2.2.0 sur carbon (~/icu4x-audit), inspection statique des Cargo.toml,
features, no_std, usages système, format de données, datagen. Aucun build lancé.