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-055 — air-thread/air-process en #![no_std] : crate air-env + spawn ergonomique gardé per-cible

Statut : Accepté (2026-06-30, ratifié par le BDFL). RFC de structure (ADR-015). Implémente ADR-048 (couche 1 std-free) et ADR-052 D7.3 (le spawn ergonomique migrera vers air-runtime). Companion d’ADR-054 (qui a retiré icu de la fermeture runtime) et d’ADR-050 (cible *-linux-air).

Catégorie : Architecture (layering, no_std). Toute évolution structurante = RFC.

Contexte

ADR-054 a levé le blocage icu de la fermeture air-runtime*-linux-air. La re-sonde a révélé le 2ᵉ mur : air-thread et air-process ne sont pas #![no_std] et fuient std :

  • air-process (3 usages, tous command.rs) : std::ffi::{CStr,CString}, std::os::unix::ffi::OsStringExt, std::env::vars_os() (héritage d’environnement dans build_envp). Le reste travaille déjà en octets (ADR-054 D4).
  • air-thread : sync.rs/channel.rs/runtime_primitives sont déjà no_std en code lib (std seulement sous #[cfg(test)]). Le blocage est thread.rs : AirThreadBuilder::spawn enrobe std::thread, délibérément (en-tête de thread.rs) : spawn(F: FnOnce()->T) exécute une closure arbitraire qui peut toucher du code glibc ; sur un thread Air sans TCB glibc-correct ce serait unsound (corruption tas, errno croisé). Cette contrainte est celle de l’HÔTE (glibc), pas de la cible : sur *-linux-air il n’y a pas de glibc, c’est air-runtime qui fournit le TCB/TLS (ADR-052).

Deux obstacles structurels : (1) air-process a besoin de l’environnement mais ne peut pas dépendre d’air-runtime (cycle : air-runtime → air-process pour exit_process) ; (2) le spawn ergonomique Air-natif relève d’air-runtime (TCB/TLS) et d’un allocateur réel (boxer la closure ; aujourd’hui placeholder AbortOnAlloc), donc indisponible immédiatement.

Décision

D1 — Nouvelle crate air-env (couche 1, no_std) : porteuse de l’environnement

Créer crates/air-env (couche 1, #![no_std], zéro dépendance — l’environnement est manipulé en octets bruts (&[u8]/*const *const u8), sans aucun type transverse ; réconciliation étape 6 : le libellé initial « deps air-sys-types seulement » était une borne haute, l’implémentation a finalement ZÉRO dépendance) qui porte la table d’environnement de processus : static ENVIRON: AtomicPtr, set_environ(envp) (appelé au démarrage), get(&[u8]) -> Option<&'static [u8]>, vars() -> impl Iterator<Item=(&[u8],&[u8])> (octets, Principe 3). Vit sous air-runtime ET air-processaucun cycle. air-runtime::env devient un mince ré-export/wrapper d’air-env (compat ; bootstrap appelle air_env::set_environ). air-process::build_envp lit air_env::vars().

Amendement (2026-07-03, décision BDFL, pré-sceau couche-1-v1.0). La borne « zéro dépendance » de D1 est relevée : air-env (et air-runtime) dépendent désormais d’air-base-core pour rendre les types chaîne-OS AirOsStr/AirOsString. Dépendance acyclique (air-base-core ne dépend que de la couche 0) et fondatrice (vocabulaire de base couche 1). Les signatures de lecture sont remodelées à leur forme finale scellable : get(&AirOsStr) -> Option<AirOsString> (copie possédée, race-safe façon std::env::var_os — au lieu d’un &'static [u8] que réécrirait un futur set_var) et vars() en paires possédées ; air-runtime::args reste &'static AirOsStr (argv immortel, zéro-copie). Motif : audit de sceau-readiness (Flag 1+2). Consigné dans DEPENDENCIES.md.

D2 — air-process passe #![no_std]

#![cfg_attr(not(test), no_std)] + extern crate std sous cfg(test). std::ffi::CStrcore::ffi::CStr ; std::ffi::CStringalloc::ffi::CString. build_envp lit l’environnement via air_env (la face hôte gnu peut rester sur std::env::vars_os derrière #[cfg(not(target_env="air"))] si utile aux tests ; la face cible lit air_env). Plus aucun std::os::unix.

D3 — air-thread passe #![no_std] ; thread.rs gardé per-cible

#![cfg_attr(not(test), no_std)]. sync.rs/channel.rs/runtime_primitives passent tels quels (std uniquement sous cfg(test)). thread.rs (spawn ergonomique sur std::thread) est gardé #[cfg(not(target_env="air"))] : il reste disponible sur l’hôte (gnu, sound sous glibc) et n’est pas compilé pour *-linux-air. Le crate compile donc no_std pour la cible (surface = sync/channel/primitives/erreurs). Le spawn ergonomique Air-natif (closure sur TCB Air) est un chantier distinct d’air-runtime, gaté sur l’allocateur réel (ADR-052 D7.3) — hors de cet ADR.

D4 — Portée : « compile » ≠ « run »

Après D1–D3, la fermeture air-runtime compile no_std pour *-linux-air (objectif étape 6, à reprouver par la re-sonde). Exécuter réellement Command/build_envp/le spawn ergonomique sur la cible reste bloqué par l’allocateur AbortOnAlloc (toute alloc abort) : c’est le chantier suivant (allocateur Air réel, « malloc échoue plutôt que hang »). Un binaire Air qui ne fait que démarrer + spawn discipliné (entrée extern "C", sans alloc) n’est pas concerné.

Conséquences

  • Étape 6 (build) débloquée : plus de fuite std dans la fermeture (à confirmer empiriquement).
  • Cycle évité par air-env (sous les deux). air-runtime::env/air-process inchangés côté appelants (ré-export).
  • air-thread sur l’hôte inchangé : le spawn ergonomique std reste pour gnu (tests, outils). Sur la cible, air-thread n’expose pas (encore) de spawn ergonomique — assumé, fourni plus tard par air-runtime + allocateur.
  • Couverture : air-env (couche 1) = 100 % ; le thread.rs cfg-gaté n’est compilé que sur gnu (couvert là). Ne PAS toucher l’--ignore-filename-regex/--exclude sans décision (ADR-035).
  • Dette d’exécution clarifiée : l’allocateur réel devient le prochain prérequis pour faire tourner (vs compiler) les features runtime.

Alternatives rejetées

  • Environnement dans air-base-core (au lieu d’air-env) : moins de crates, mais l’environ est un global mutable étranger au caractère « types purs » d’air-base-core. Rejeté pour la netteté (responsabilité unique).
  • air-process dépend d’air-runtime pour l’environ : cycle (air-runtime → air-process). Rejeté.
  • Réimplémenter le spawn std-free maintenant (au lieu du cfg-gate) : exige résoudre le cycle air-thread↔air-runtime et l’allocateur réel ; relève d’un RFC propre (le doc de thread.rs le dit). Disproportionné pour débloquer l’étape 6. Déféré (chantier allocateur).

Mise en œuvre (esquisse, code headless après ratification)

  1. crates/air-env : extraire ENVIRON/set_environ/get/vars d’air-runtime::env ; air-runtime::env ré-exporte/wrappe ; bootstrap appelle air_env::set_environ. 100 % cov.
  2. air-process : #![no_std] ; core/alloc::ffi ; build_envpair_env (cfg face hôte). Tests/doctests adaptés. 100 % cov.
  3. air-thread : #![no_std] ; thread.rs#[cfg(not(target_env="air"))] ; reste tel quel. 100 % cov.
  4. Re-sonde *-linux-air (carbon x86 + raspi/CI) : fermeture air-runtime compile sans std.
  5. Doc : ADR-055, registre, SUMMARY, INDEX, DEPENDENCIES (air-env + maj process/thread), renvois.