Contexte et motivations

DHH, créateur de Ruby on Rails, a commandé la réécriture de l'application Campfire Once, initialement écrite en Rails, vers Rust, puis vers Elixir et Go. L'objectif affiché était de tester la capacité des agents d'IA à produire du code sans intervention humaine, en s'appuyant sur l'expérience du développeur mais sans lecture du résultat.

Différences fonctionnelles et architecturales

Les versions générées diffèrent sur plusieurs critères non fonctionnels. La version Rust ne conserve pas la compatibilité totale avec l'API Rails : le jeton CSRF est supprimé pour faciliter le caching, et Redis est remplacé par des files d'attente internes, modifiant la persistance des notifications. En revanche, la version Elixir conserve davantage les comportements d'origine, notamment la gestion des tokens et l'utilisation de Redis.

Ces écarts montrent que, sans consignes précises, les agents décident arbitrairement de prioriser latence, mémoire ou robustesse, ce qui rend la comparaison entre langages peu pertinente.

Analyse des performances et benchmarks

Les tests de charge menés par Zach Daniels révèlent des écarts marqués. Sous un benchmark fermé (clients en boucle qui envoient une requête dès la réponse précédente), la version Rust a traité environ quatre fois plus de requêtes, mais n'a délivré que 1 % des notifications sur 6 000‑7 000 messages. La version Elixir a maintenu 100 % de livraison sur ~1 700 notifications, indiquant une meilleure gestion du back‑pressure.

Le problème provient de l'implémentation asynchrone en Rust. Le runtime Tokio utilise un ordonnancement coopératif : si une tâche ne libère pas le contrôle, les autres tâches restent bloquées. Certaines opérations SQL sont exécutées de façon bloquante dans des tâches async, et des verrous synchrones (

tokio::sync::Mutex
) sont employés alors que le temps de blocage dépasse quelques millisecondes, ce qui empêche les workers de progresser.

En Elixir, le modèle BEAM offre un ordonnancement préemptif, mais la version présentée utilise un seul processus séquentiel pour toutes les requêtes SQL, limitant le parallélisme des lectures. Ainsi, chaque langage montre des points faibles différents : Rust souffre de blocages coopératifs, Elixir de sous‑exploitation du parallélisme.

Implications et limites

Ces résultats soulignent que la simple traduction de code ne garantit ni performance ni conformité fonctionnelle. Les décisions architecturales (choix du cache, du système de files, du modèle de concurrence) doivent être explicitement spécifiées dans le prompt, sinon les agents les résolvent par défaut, souvent de façon sous‑optimale.

De plus, les benchmarks fermés ne mesurent qu'un aspect (débit) et masquent d'autres critères comme la fiabilité des livraisons d'événements. Un test à taux d'arrivée constant aurait fourni une vision plus équilibrée de la capacité de chaque implémentation à gérer la charge réelle.

En conclusion, la réécriture automatisée montre que la maîtrise du code reste indispensable : comprendre les modèles de concurrence, les exigences de compatibilité et les métriques de performance est essentiel pour éviter des régressions majeures.