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

Phase B1 — Structure physique et style guide général

Document de setup phase 0 — Version 1.0

Vue d’ensemble

La Phase B1 couvre comment le code Air est organisé physiquement et comment il est écrit. Ce sont les conventions qu’un contributeur rencontre dès son premier patch.

L’objectif est d’avoir un projet où un développeur découvrant n’importe quel fichier reconnaît immédiatement les conventions qui s’appliquent.

Section 1 : Structure physique du workspace

Mono-repo Cargo workspace

Air est organisé en un mono-repo unique contenant un workspace Cargo. Ce choix offre plusieurs avantages :

  • Builds atomiques.
  • Refactorings transverses faciles.
  • Tests d’intégration triviaux entre crates.
  • Cargo.lock unique partagé.

C’est la convention de facto pour les projets Rust de taille industrielle.

Structure des dossiers à la racine

air/
├── Cargo.toml                  # Manifest du workspace
├── Cargo.lock                  # Lockfile partagé
├── rust-toolchain.toml         # Version Rust pinnée
├── rustfmt.toml                # Configuration de formatage
├── .clippy.toml                # Configuration de lints
├── deny.toml                   # Configuration cargo-deny
├── EXCEPTIONS.md               # Exceptions au Principe 6
├── README.md                   # Présentation publique
├── LICENSE                     # MPL 2.0
├── CHARTER.md                  # Lien vers la Charte
├── ENGINEERING_PRINCIPLES.md   # Lien vers les Principes
├── CODE_OF_CONDUCT.md          # Contributor Covenant
├── CONTRIBUTING.md             # Guide de contribution
├── SECURITY.md                 # Politique de sécurité
│
├── docs/                       # Documentation projet
│   ├── adrs/                   # Architecture Decision Records
│   ├── vision/                 # Vision document (FR + EN)
│   ├── specs/                  # Specifications techniques
│   └── guides/                 # Guides développeur
│
├── crates/                     # Toutes les crates Air
│   ├── air-sys-types/
│   ├── air-sys-syscall/
│   ├── air-runtime-core/
│   ├── air-runtime-io/
│   └── ...
│
├── tools/                      # Outils internes
│   ├── air-abi-check/
│   ├── air-symver/
│   └── air-deprecation-tracker/
│
├── tests/                      # Tests d'intégration cross-crate
├── benches/                    # Benchmarks transverses
├── .github/                    # Configuration GitHub
│   ├── workflows/              # CI
│   └── PULL_REQUEST_TEMPLATE.md
└── scripts/                    # Scripts de build, release, etc.

Conventions de nommage des crates

Préfixe air-. Toutes les crates Air commencent par air-.

Hiérarchie par modules logiques.

  • air-sys-* : couche 0 (système).

    • air-sys-types : types fondamentaux.
    • air-sys-syscall : wrappers de syscalls.
  • air-runtime-* : couche 1, runtime async.

    • air-runtime-core, air-runtime-io, air-runtime-time, etc.
  • air-object-* : couche 2, modèle d’objet.

    • air-object-core, air-object-derive.
  • air-aircom-* : couche 2, IPC.

    • air-aircom-protocol, air-aircom-transport.
  • air-compositor-* : couche 3.

  • air-tui-*, air-ui-* : couche 4.

  • air-launcher-* : couche 5.

Crate façade par couche. Chaque couche peut avoir une crate façade qui réexporte les éléments principaux.

Visibilité des crates.

  • Publiques : destinées à crates.io.
  • Internes : publish = false ou registre restreint.

Configuration globale du workspace

[workspace]
resolver = "2"
members = [
    "crates/*",
    "tools/*",
]

[workspace.package]
version = "0.1.0"
edition = "2024"
rust-version = "1.96.0"
license = "MPL-2.0"
authors = ["Air contributors"]
repository = "https://github.com/air-desktop-project/air"

[workspace.dependencies]
bitflags = "2.4"
thiserror = "1.0"
# Pas de `rustix` : la couche 0 appelle les syscalls directement via
# `core::arch::asm!`, sans dépendance de bindings externe (cf. EXCEPTIONS.md
# et la décision de socle consignée dans macro-architecture-fr.md).

[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "deny"
unused_must_use = "deny"
unreachable_pub = "warn"

[workspace.lints.clippy]
all = "warn"
pedantic = "warn"
cast_possible_truncation = "deny"
cast_sign_loss = "deny"
cast_lossless = "deny"
arithmetic_side_effects = "warn"
undocumented_unsafe_blocks = "deny"
missing_safety_doc = "deny"
missing_errors_doc = "warn"
missing_panics_doc = "warn"

Fichier rust-toolchain.toml

[toolchain]
channel = "1.96.0"
components = ["rustfmt", "clippy", "rust-src", "llvm-tools-preview"]
targets = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"]
profile = "minimal"

La version Rust est pinnée explicitement.

Section 2 : Style guide général

Nommage

  • Crates : kebab-case avec préfixe air-.
  • Modules : snake_case.
  • Types : UpperCamelCase.
  • Traits : UpperCamelCase.
  • Fonctions, méthodes, variables : snake_case.
  • Constantes : SCREAMING_SNAKE_CASE.
  • Lifetimes : single lowercase letter ou nom court explicite.

Structure des fichiers source

Ordre des éléments :

  1. Module-level doc comment (//!).
  2. Imports (use).
  3. Constantes publiques.
  4. Constantes privées.
  5. Types publics.
  6. Types privés.
  7. Implémentations (impl blocks).
  8. Fonctions publiques libres.
  9. Fonctions privées libres.
  10. Tests (#[cfg(test)] mod tests).

Imports

Trois groupes séparés par une ligne vide :

  1. Imports de core et std.
  2. Imports de dépendances externes.
  3. Imports de crates Air.
#![allow(unused)]
fn main() {
use core::ffi::CStr;
use std::time::Duration;

use bitflags::bitflags;
use thiserror::Error;

use air_sys_types::{OwnedFd, Pid};
use air_sys_syscall::process;
}

Pas de use *. Toujours nommer les imports explicitement.

Visibilités

  • pub : exposé hors de la crate. Soigneusement choisi.
  • pub(crate) : utilisable dans la crate mais pas exposé.
  • pub(super) : utilisable dans le module parent.
  • Privé : utilisable uniquement dans le module.

Tout symbole pub engage la stabilité ABI selon l’ADR-012.

Conventions sur les modules

  • Un module par concept.
  • Réexports en lib.rs.
  • Pas de modules « util » génériques.

Conventions sur les types

  • Newtypes systématiques pour les identifiants.
  • Préférence pour les enums sur les booléens multiples.
  • #[non_exhaustive] sur les enums publics.
  • #[must_use] sur les types qui doivent être consommés.

Conventions sur les fonctions

  • Fonctions courtes (typiquement < 50 lignes).
  • Pas plus de 5-7 paramètres.
  • Préférer les références aux ownerships sauf nécessité.
  • Pas de paramètres bool.

Conventions sur les erreurs

  • Tout Result retourné a un type d’erreur précis.
  • thiserror pour les types d’erreur enrichis.
  • Les conversions d’erreur sont explicites via From.
  • Pas de panic! dans le code de production sauf invariant interne.
  • Pas de unwrap() ou expect() dans le code de production.

Conventions rustdoc

Structure d’une docstring :

  1. Résumé en une ligne.
  2. Description détaillée.
  3. Section # Examples (recommandée).
  4. Section # Errors (obligatoire pour Result).
  5. Section # Panics (obligatoire si peut paniquer).
  6. Section # Safety (obligatoire pour unsafe).

Les exemples doivent compiler. Le CI exécute cargo test --doc.

Section 3 : Conventions sur unsafe

Discipline générale

  • Pas d’unsafe au-dessus de la couche 1 sauf justification exceptionnelle.
  • Les besoins éventuels en unsafe sont concentrés dans des modules dédiés et audités.

Format des commentaires SAFETY

Chaque bloc unsafe est précédé d’un commentaire // SAFETY: qui explique pourquoi l’invariant est respecté :

#![allow(unused)]
fn main() {
pub fn pid(&self) -> Pid {
    // SAFETY: getpid never fails and always returns a positive PID per POSIX.
    unsafe { Pid::new_unchecked(libc::getpid() as i32) }
}
}

Le lint undocumented_unsafe_blocks (configuré en deny) refuse les blocs unsafe sans commentaire.

Encapsulation des unsafe

Un bloc unsafe doit être le plus court possible, idéalement une seule ligne.

Revue obligatoire

Tout PR qui introduit ou modifie un bloc unsafe demande une revue par un mainteneur senior.

Lints associés

  • unsafe_op_in_unsafe_fn : deny.
  • undocumented_unsafe_blocks : deny.
  • missing_safety_doc : deny.

Récapitulatif Phase B1

DomaineConvention principale
RepoMono-repo Cargo workspace
Structurecrates/, tools/, docs/, tests/, benches/
Nommage cratesPréfixe air-, hiérarchie par couche
Nommage RustConventions Rust standard, newtypes systématiques
ImportsTrois groupes (core/std, externe, Air)
VisibilitésExplicites, pub engage la stabilité
RustdocSections structurées, exemples qui compilent
unsafeCommentaires SAFETY obligatoires, encadrement strict

Licence du document : MPL 2.0 Statut : Document de setup phase 0.