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 = falseou 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-caseavec préfixeair-. - 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 :
- Module-level doc comment (
//!). - Imports (
use). - Constantes publiques.
- Constantes privées.
- Types publics.
- Types privés.
- Implémentations (
implblocks). - Fonctions publiques libres.
- Fonctions privées libres.
- Tests (
#[cfg(test)] mod tests).
Imports
Trois groupes séparés par une ligne vide :
- Imports de
coreetstd. - Imports de dépendances externes.
- 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
Resultretourné a un type d’erreur précis. thiserrorpour 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()ouexpect()dans le code de production.
Conventions rustdoc
Structure d’une docstring :
- Résumé en une ligne.
- Description détaillée.
- Section
# Examples(recommandée). - Section
# Errors(obligatoire pourResult). - Section
# Panics(obligatoire si peut paniquer). - Section
# Safety(obligatoire pourunsafe).
Les exemples doivent compiler. Le CI exécute cargo test --doc.
Section 3 : Conventions sur unsafe
Discipline générale
- Pas d’
unsafeau-dessus de la couche 1 sauf justification exceptionnelle. - Les besoins éventuels en
unsafesont 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
| Domaine | Convention principale |
|---|---|
| Repo | Mono-repo Cargo workspace |
| Structure | crates/, tools/, docs/, tests/, benches/ |
| Nommage crates | Préfixe air-, hiérarchie par couche |
| Nommage Rust | Conventions Rust standard, newtypes systématiques |
| Imports | Trois groupes (core/std, externe, Air) |
| Visibilités | Explicites, pub engage la stabilité |
| Rustdoc | Sections structurées, exemples qui compilent |
unsafe | Commentaires SAFETY obligatoires, encadrement strict |
Licence du document : MPL 2.0 Statut : Document de setup phase 0.