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-053 — La libc Air s’appuie sur icu4x pour ctype/locale/i18n (caractères, casse, collation, normalisation, segmentation)

Statut : Accepté (2026-06-30, ratifié par le BDFL). Companion d’ADR-046 (libc Air), ADR-047 (construction libc) et ADR-016 (i18n / icu4x). Corrige/precise ADR-046 D7 (« libm exclu v1 », « hors wide-char lourd » — devenus obsolètes, cf. D6). S’appuie sur l’audit docs/notes/audit-icu4x-rust-pur-fr.md.

Catégorie : Architecture (libc / i18n / fondateur). Évolution structurante = RFC (ADR-015).

Contexte

Pendant des décennies, les développeurs C ont subi les interprétations par implémenteur de libc du traitement des caractères et des locales : isprint/isalpha dépendant d’une locale globale floue, casse cassée hors ASCII, strcoll de qualité variable, locale C naïve, « implementation-defined » partout. Pour avoir des programmes portables et corrects en i18n, le développeur sérieux embarque icu4x par-dessus sa libc. Même constat côté Rust : std (str/String) ne repose pas sur icu4x — il embarque ses propres mini-tables Unicode (propriétés, casse locale-indépendante) et ne fait ni collation ni normalisation ni segmentation locale-aware ; pour ça, on importe des crates externes (icu4x). Les deux écosystèmes laissent donc le i18n correct en bolt-on.

Air refuse cet héritage. La doctrine libc (ADR-046 D3/D5 : « POSIX = objectif fonctionnel, pas conformité ; SHOULDMUST ; kernel = bible, pas POSIX ») dit déjà : là où POSIX est faible, on ne reproduit pas la faiblesse, on donne le comportement défini et correct. L’i18n est le terrain emblématique de cette doctrine.

Décision

D1 — Le ctype/locale/i18n de la libc Air repose sur icu4x (couche 1)

La surface C de traitement de caractères, locales et i18n s’appuie sur icu4x (déjà adopté en couche 1, ADR-016) :

  • classification (isalpha/isdigit/isprint/iswalpha…) → icu_properties ;
  • casse (towupper/towlower/wcs*/towctrans) → icu_casemap ;
  • collation (strcoll/strxfrm/wcscoll) → icu_collator (UCA) ;
  • normalisation (NFC/NFD/NFKC/NFKD, extensions) → icu_normalizer ;
  • segmentation (graphème/mot/ligne/phrase, extensions) → icu_segmenter ;
  • locales (BCP-47, ADR-016 ; pas les noms POSIX) et calendriersicu_locale/icu_calendar.

Le développeur C n’a plus à importer icu4x : Air le lui offre au socle. Et là où POSIX a des trous (segmentation, normalisation, casse pleine, collation correcte), Air expose le standard Unicode plutôt que d’imposer la faiblesse POSIX.

D2 — Mapping POSIX ↔ icu4x : granularité (le vrai travail)

L’API POSIX ctype est mono-caractère + locale globale ; icu4x est niveau chaîne + locale explicite + sans état. Réconciliation :

  • classification mono-code-point (isX/iswX) → icu_properties (mapping direct) ;
  • casse simple mono-caractère (towupper(c) rend un code-point) → mapping simple 1→1 ; la casse pleine/locale (1→N, règles turques/grecques) sort par les fonctions niveau chaîne (wcs*, ou extensions Air) ;
  • collation/normalisation/segmentation = intrinsèquement niveau chaîne → fonctions strcoll/strxfrm/wcscoll + extensions Air (normaliser, segmenter) exposées au C.

D3 — Locale globale POSIX réconciliée avec le « zéro globale » d’Air

POSIX exige setlocale/une locale globale ; Air veut zéro globale + locale explicite (ADR-046 D4). On fournit donc une locale courante thread-local (même modèle que errno-in-TCB, ADR-047/ADR-049) qui alimente les appels icu4x explicites. Interne en BCP-47 ; les noms de locale POSIX (fr_FR.UTF-8…) sont mappés en BCP-47.

D4 — Garder le byte-level rapide ; icu4x là où il ajoute de la correction

Les chemins triviaux et chauds restent simples et rapides (fast-path ASCII d’isdigit, strcmp=memcmp, strlen…). icu4x est sollicité pour les opérations locale-aware/Unicode (collation, casse pleine, normalisation, segmentation, propriétés non-ASCII), pas pour le byte-level. On n’alourdit pas le commun.

D5 — Écarts à la lettre POSIX documentés (transparence = valeur d’Air)

Là où Air fait Unicode-correct au lieu du quirk C-locale, l’écart est documenté par fonction (doc polyglotte, ADR-027). Le développeur sait exactement ce que fait la fonction — c’est la promesse « une libc qui fait ce qu’elle dit ».

D6 — Conséquences sur le périmètre ADR-046 (ce qui devient obsolète)

  • libm n’est PLUS « exclu v1 » : icu4x exige un libm no_std (math de calendrical_calculations pour les calendriers — incontournable — et du LSTM de icu_segmenter). ⇒ ADR-046 D7 « libm exclu de la v1 » est OBSOLÈTE : libm no_std est un prérequis de la i18n Air. (Décision « libm réécrit vs vendoré » = question ouverte.)
  • Le wide-char/wcs n’est PLUS « hors périmètre » : wchar/wcs*/towctrans est précisément la surface chaîne où la puissance icu4x s’expose. ⇒ ADR-046 D7 « hors wide-char lourd » est OBSOLÈTE dans sa forme absolue : le wcs i18n-aware fait partie du cœur (adossé à icu4x).

D7 — Un seul socle icu4x, trois consommateurs (dual-face étendu)

La même i18n couche 1 (icu4x Air) sert : la libc C (cet ADR), le futur std de *-linux-air (ses méthodes char/str, qui sinon dupliqueraient des mini-tables), et le pal. Un i18n maîtrisé, trois surfaces (prolonge le motif dual-face, ADR-046/047).

Conséquences

  • L’i18n correcte est par défaut dans la libc Air ; le développeur C n’embarque plus icu4x ; les faiblesses POSIX ne sont plus imposées.
  • Prérequis débloqués : libm no_std (D6), le port icu4x couche 1 (audit : socle no_std calcul-pur + petites crates utilitaires vendorables + pipeline de données CLDR/UCD).
  • Taille : une libc qui bake des tables CLDR est plus grosse que la locale minimale musl ; mitigeable (tables feature-gated / chargeables) — décision d’implémentation.
  • Compat : du code C dépendant du comportement exact C-locale peut différer ; risque faible pour les cibles v1 (OpenSSH, ASCII-centré), couvert par D5 (documentation).

Alternatives écartées

  • Locale POSIX minimale (façon musl/glibc) : c’est la faiblesse historique qu’on rejette.
  • Laisser le bolt-on icu4x au développeur (statu quo C et Rust std) : Air fait mieux — i18n correcte au socle.
  • icu4x seulement aux couches hautes (UI/formats) : raterait la surface C ; or c’est que des décennies de programmes ont souffert.

Questions ouvertes

  • libm : réécrire un libm Air no_std, ou vendorer libm (exception 80 % nommée) ? (Bloque calendar + LSTM — premier maillon.)
  • LSTM : segmentation LSTM (floats + modèle) ou dictionnaire seul en v1 ?
  • Datagen : réutiliser celui d’icu4x (pragmatique) ou réimplémenter (host, std) ? Sources CLDR/UCD amont dans tous les cas.
  • Calendrier de réalisation : la i18n Air vient après le runtime/libc de base, ou en parallèle ?

Licence du document : MPL 2.0.