Présentation
Le billet de Pranoy Dutta recense trois mécanismes de langage qui, selon lui, améliorent la sûreté et la lisibilité du code sans imposer de surcharge d’exécution notable. Il s’appuie sur Crystal pour le flow typing, sur Rust pour le borrow checking et sur D pour la programmation par contrat. Chaque idée repose sur des règles de typage ou de vérification qui sont appliquées à la compilation.
Flow typing
Crystal, langage compilé à typage statique inspiré de Ruby, autorise une variable à changer de type au cours de son exécution. L’exemple fourni montre my_var = 5 (type Int32) puis, sous une condition, my_var = "hello!" (type String). Après la branche conditionnelle, le compilateur attribue à my_var le type union Int32 | String. Cette union oblige le développeur à insérer un test if my_var.is_a?(String) avant d’appeler une méthode propre à String. Le même principe apparaît dans TypeScript, où le « type narrowing » restreint le type d’une variable après un test de garde. Le mécanisme repose sur l’inférence de flux : le compilateur suit les chemins d’exécution pour déterminer les ensembles de types possibles, ce qui donne l’impression d’un langage dynamique tout en conservant la vérification à la compilation.
Borrow checking
Rust empêche les data races grâce à son borrow checker. Le texte rappelle trois règles : (1) aucune emprunt ne doit survivre au propriétaire, (2) on ne peut avoir qu’une référence mutable (&mut T) ou plusieurs références immutables (&T), jamais les deux simultanément, et (3) la durée de vie d’un emprunt est limitée à son bloc lexical. Cette discipline ressemble à un verrou lecteurs‑écrivains, mais elle est résolue à la compilation, ce qui élimine tout coût d’exécution supplémentaire. Le coût se manifeste uniquement lors de la compilation, où le vérificateur analyse les graphes de vie des références. Le texte souligne que la complexité ajoutée est inhérente à la programmation concurrente, mais qu’elle reste « zero‑cost » du point de vue du runtime.
// Exemple Rust simplifié
fn main() {
let mut data = 0;
let r1 = &data; // emprunt immuable
// let r2 = &mut data; // illégal : mutable et immutable simultanés
println!("{}", r1);
}
Programmation par contrat
D propose une syntaxe dédiée aux invariants, aux pré‑conditions (in) et aux post‑conditions (out). Le langage différencie assert, qui signale une violation de logique interne, de enforce, qui lève une exception liée à une condition externe (par ex. entrée utilisateur invalide). L’exemple de la fonction daysInFebruary montre un out (result) qui garantit que le résultat est toujours 28 ou 29. Au niveau de la classe BankAccount, le bloc invariant() impose que le solde reste non négatif, tandis que chaque méthode possède des clauses in et out qui sont vérifiées automatiquement à l’entrée et à la sortie. Cette approche évite la duplication de vérifications manuelles et rend explicite le contrat de chaque API.
class BankAccount {
private double balance;
invariant() { balance >= 0; }
this(double initialBalance) in (initialBalance >= 0) { balance = initialBalance; }
void deposit(double amount) in (amount > 0) out (balance == balance + amount) { balance += amount; }
}