J’ai récemment porté vers Rust une codebase Django vieille de sept ans, d’environ 200 000 à 300 000 lignes. Le portage m’a pris un mois et demi. Même avec une approche initialement assez naïve, certains bugs auparavant remontés dans Sentry ont disparu, et j’ai constaté une baisse de 40 à 60 % de mes coûts de serveurs.

Mais le changement le plus intéressant concerne ma manière de choisir un langage.

Je suis développeur solo, avec sept ans d’expérience en Python et Django et deux ans en Rust. Avant cette migration, j’avais consacré environ un an à apprendre Rust dès que possible : Rustlings, exercices, lecture et apprentissage des idiomes. Ce choix s’appuyait sur une pratique des deux langages et sur une contrainte concrète : développer et maintenir moi-même le produit.

Mon point de vue n’est pas neutre. Après sept ans de Django, une certaine lassitude existe probablement : trop de bugs découverts à l’exécution, trop de temps passé à essayer de maintenir un typage satisfaisant. Je connais aussi les avantages de Python. Sa souplesse, sa concision et son écosystème m’ont permis de livrer pendant des années.

Cette migration m’a toutefois amené à reconsidérer une question : quand les agents écrivent une part croissante du code, les qualités qui rendent un langage agréable pour un humain restent-elles les critères les plus importants ?

Je m’attendais à corriger beaucoup plus de code

Au départ, je pensais que les agents produiraient un Rust assez approximatif : des erreurs, des contournements du système d’emprunt, du code qui compile sans être idiomatique.

J’ai donc commencé par tout relire, ligne par ligne. Cette relecture représentait des milliers de lignes réparties sur environ 300 à 400 fichiers. J’y ai parfois consacré des après-midi entiers, à examiner chaque ligne et à itérer avec Claude et Codex. J’ai aussi confronté le code aux revues de différents modèles pour chercher ce qui m’aurait échappé.

J’ai finalement trouvé beaucoup moins d’erreurs que prévu. Les problèmes que je relevais concernaient surtout des choix d’architecture ou des répétitions. Le code me paraissait globalement bon et idiomatique.

Ces relectures ne prouvent pas l’absence de bugs, et plusieurs modèles peuvent partager les mêmes angles morts. Elles ont néanmoins contredit mon attente initiale : je pensais devoir reprendre beaucoup plus de Rust.

C’est ce décalage qui m’a fait revoir mon jugement. L’image que je gardais des agents quelques années auparavant ne correspondait plus à ce que j’observais sur ce portage. Mon expérience du langage me permettait d’évaluer leur travail, tandis que le compilateur leur donnait déjà les moyens de corriger une partie des erreurs avant ma revue.

Quand les contraintes deviennent un avantage

Python a construit une partie de son succès sur une promesse convaincante : un langage facile à apprendre, à lire et à écrire. Quand un humain produit chaque ligne, cette simplicité a une valeur immédiate.

Django pousse cette logique plus loin. Son ORM, ses migrations, son admin et ses conventions permettent de livrer beaucoup avec peu de code. Class-based views, décorateurs, forms, signaux, managers et mixins évitent de réécrire des comportements courants.

Cette compression a aussi une contrepartie : une partie du comportement devient implicite. Comprendre ce qui se passe réellement demande parfois de traverser plusieurs couches d’abstraction et de connaître les conventions du framework.

Rust demande davantage d’apprentissage et rend beaucoup de contrats explicites : types, signatures, valeurs absentes, erreurs possibles, règles d’emprunt. Cela représente un effort à l’écriture, mais fournit aussi davantage de propriétés vérifiables automatiquement.

Avec les agents, le poids de ces compromis change. Deux cents lignes générées et vérifiées automatiquement ne mobilisent pas nécessairement plus de temps humain que trente lignes très abstraites qu’il faut ensuite comprendre et déboguer.

La verbosité seule n’apporte aucune garantie. Ce qui compte, c’est ce qu’elle rend explicite et vérifiable. Et la lisibilité reste essentielle : mes après-midi de relecture me l’ont rappelé.

Mais la facilité d’écriture perd une partie de son poids lorsque je ne suis plus celui qui écrit chaque ligne.

C’est ce que j’appelle un heureux accident pour le développement agentique : des choix de conception destinés à rendre les programmes plus sûrs se révèlent particulièrement utiles pour encadrer du code généré.

Le compilateur comme partenaire de l’agent

Un agent dispose d’un signal utile lorsqu’il peut s’appuyer sur des vérifications déterministes :

modification → vérification → erreur structurée → correction

Rust fournit beaucoup de signal dans cette boucle. Le compilateur vérifie les types, l’ownership, les durées de vie et certaines propriétés de concurrence. Result représente une réussite ou une erreur dans le type de retour. Option rend explicite l’absence éventuelle d’une valeur. Un match doit couvrir toutes les possibilités. Clippy complète ces vérifications avec d’autres diagnostics.

Pour un humain, ces contraintes peuvent ralentir l’écriture. Pour l’agent, elles donnent aussi des indications de correction. Il peut constater où un contrat est rompu, modifier le code et relancer la vérification.

Cela ne garantit pas qu’une erreur sera bien traitée. On peut ignorer volontairement un résultat, choisir une mauvaise stratégie de récupération ou provoquer un panic avec un unwrap mal placé. Mais les situations à traiter sont représentées dans le code et une partie de sa cohérence est vérifiée avant l’exécution.

En Python, davantage de problèmes peuvent rester invisibles jusqu’à ce que le chemin concerné soit exécuté : attribut absent, nom incorrect, valeur inattendue ou contrat implicite cassé.

Pris séparément, ces bugs sont souvent simples à corriger. Mais leur coût ne se limite pas aux trente secondes nécessaires pour changer une ligne. Il faut les découvrir, les reproduire, interrompre le travail, vérifier le correctif et parfois redéployer.

Un agent peut modifier beaucoup de code très vite. S’il ne reçoit aucun signal sur un chemin cassé, sa capacité à corriger rapidement ne nous aide pas encore.

Le typage Django reste un effort supplémentaire

Python dispose de véritables outils de vérification statique. Pyright, mypy et django-stubs peuvent détecter de nombreux problèmes. Pydantic peut valider les données aux frontières. Une CI stricte peut rendre ces vérifications obligatoires dans le processus de livraison.

Je les ai utilisés. Mon reproche porte surtout sur l’effort nécessaire pour maintenir une couverture satisfaisante avec Django.

La métaprogrammation, les métaclasses et les comportements dynamiques du framework compliquent cette tâche. J’ai souvent eu l’impression de me battre avec l’outillage pour décrire proprement un comportement que Django construit à l’exécution.

On finit par accepter des exceptions, des contournements ou une couverture partielle. Cette couverture reste utile, mais elle laisse des angles morts.

Les annotations constituent un contrat que des outils peuvent vérifier. Python peut néanmoins exécuter le programme sans faire respecter ce contrat. Avec Rust, la cohérence des types est une condition pour construire le programme. Elle fait partie du fonctionnement normal du langage.

Pour un développeur solo travaillant avec des agents, cette différence compte : j’ai moins besoin d’entretenir une couche supplémentaire pour obtenir cette vérification.

Elle change aussi ce que je demande aux tests. Je préfère concentrer leur effort sur les règles métier, les permissions, les transactions, les migrations et les intégrations réelles. Les propriétés déjà vérifiées par le compilateur n’ont pas toutes besoin d’être redémontrées par des tests défensifs.

Le temps de compilation n’est pas mon temps

La compilation reste un coût. Une boucle lente retarde le résultat et peut bloquer la suite du travail. Il serait absurde de prétendre que ce temps n’a aucune importance.

Mais le temps de l’agent et mon temps ne sont pas interchangeables.

Une minute pendant laquelle l’agent corrige seul ses erreurs n’équivaut pas à une minute pendant laquelle je dois intervenir. Je peux lui laisser terminer sa boucle et revenir sur un changement qui a déjà passé les vérifications disponibles.

Comparer uniquement la vitesse du premier jet, le nombre de lignes ou la durée d’une vérification passe à côté de cette différence.

Ce qui m’intéresse, c’est le temps nécessaire pour obtenir un changement vérifié, et surtout le nombre d’interventions humaines pour y parvenir.

La courbe d’apprentissage de Rust existe également. Pour un débutant, elle peut représenter un obstacle important. Dans mon cas, avec une expérience préalable et les agents que j’utilise aujourd’hui, elle ne domine plus le travail de la même manière.

Je dois toujours comprendre le langage pour juger le résultat. Mais je n’assume plus seul tout le coût de son écriture.

Un outillage cohérent aide aussi

J’apprécie la cohérence de l’écosystème de développement autour de Cargo : construire, vérifier, tester et gérer les dépendances suivent des conventions communes, avec rustfmt et Clippy pour compléter l’ensemble.

Python a beaucoup progressé, notamment avec uv et Ruff. Les différents outils ne sont d’ailleurs pas tous concurrents : certains gèrent les environnements, d’autres les dépendances, le formatage ou la vérification.

Il reste néanmoins davantage de choix et de configurations à assembler selon les projets. Dans mon expérience, cela ajoute de la friction.

Pour les agents, des commandes et des conventions prévisibles facilitent aussi la boucle de travail. L’intérêt dépasse le confort de l’installation initiale : il concerne chaque modification qu’il faudra ensuite vérifier.

Ce que Django fait toujours mieux

Django conserve des avantages considérables. Un agent peut écrire une intégration manquante ; il ne recrée pas instantanément des années de retours d’expérience, de compatibilité et de corrections.

Les migrations restent pour moi un point fort. Génération automatique, historique, dépendances et migrations de données forment un ensemble très productif. Avec SQLx, je versionne mes migrations SQL, mais je retrouve moins d’assistance pour faire évoluer le schéma.

Pour l’accès aux données, je préfère néanmoins SQLx avec un repository pattern :

handler ou service → repository → SQLx → base de données

Le SQL reste visible, les transactions sont explicites et les responsabilités sont regroupées derrière une frontière claire. Lorsque ce découpage est bien conçu, il peut aussi limiter le contexte nécessaire à une modification par l’agent.

Ce choix ajoute du code. J’accepte ce coût pour obtenir des accès aux données que je trouve plus faciles à inspecter.

Django Admin reste lui aussi difficile à battre pour un back-office CRUD. Son avantage devient moins décisif lorsque l’outil interne demande des workflows spécifiques ou une interface éloignée des modèles de la base. L’IA réduit le coût d’une interface sur mesure, mais les permissions, les validations et la traçabilité restent à concevoir.

Enfin, dans mes usages avec un frontend séparé et une authentification externalisée, je bénéficie moins des templates, des formulaires et de l’intégration complète de Django. Cela modifie l’équilibre sans enlever sa valeur pour les projets qui exploitent pleinement cet ensemble.

Les ressources consommées comptent, même à petite échelle

Sur cette migration, j’ai constaté une baisse d’environ 40 à 60 % de mes coûts de serveurs.

C’est le résultat d’un projet particulier, avec les changements d’implémentation qu’un portage implique. Ce n’est pas un benchmark qui isolerait l’effet du langage, ni une économie que je promets à chaque équipe.

Mais je ne partage pas l’idée que les ressources consommées seraient un sujet secondaire par principe pour une jeune entreprise. La RAM, le CPU, la taille des instances et leur nombre se retrouvent sur la facture.

Le déploiement d’un backend compilé sous forme de binaire peut aussi simplifier l’exploitation. Et pouvoir rester plus longtemps sur une infrastructure simple a une valeur qui dépasse le seul prix du CPU.

Ces économies ne suffisent pas à justifier n’importe quelle réécriture. Elles font néanmoins partie des résultats concrets que j’ai obtenus.

Le compilateur ne comprend pas le produit

Un agent peut produire du Rust qui compile et choisir une mauvaise abstraction, une frontière transactionnelle incorrecte ou une requête catastrophique. Il peut ajouter des couches inutiles simplement parce que produire davantage de code lui est facile.

Le compilateur ne sait pas si le pricing est faux, si une migration va bloquer une grosse table ou si une règle métier a été mal comprise. Il ne vérifie pas non plus que la base de production correspond effectivement aux hypothèses du programme.

Le développeur reste responsable de l’architecture, du contexte transmis à l’agent et du comportement livré. La CI, les tests approfondis et la revue restent indispensables.

Mon expérience a également une limite : je portais un produit existant, dont je connaissais déjà le métier. Cela facilite l’évaluation du résultat. Un projet neuf, aux besoins encore mouvants, peut conduire à d’autres compromis.

Ce que les agents changent dans mon choix

Dans mon travail et dans ma boîte, le développement agentique fait déjà partie du quotidien. Mon choix de langage doit tenir compte de cette réalité.

La facilité d’écriture humaine garde de la valeur, mais elle pèse moins qu’avant. Les contrats explicites, les vérifications automatiques et la capacité de l’agent à corriger seul prennent davantage de place.

TypeScript participe aussi à ce mouvement en apportant des vérifications utiles avant l’exécution, même si ses garanties sont différentes et plus limitées que celles de Rust.

Pour mes backends, cette évolution rend Rust beaucoup plus compétitif. Ses contraintes ne disparaissent pas : une partie du coût est prise en charge par l’agent, tandis que je continue de bénéficier des vérifications qu’elles permettent.

C’est pour cela que je parle d’un heureux accident. Quand produire du code devient plus facile, ce qu’on peut vérifier automatiquement sur ce code prend davantage de valeur.

Transparence sur l’usage de l’IA

Ce billet part d’un texte que j’ai rédigé moi-même ; l’IA m’a aidé à structurer certains passages et à vérifier des détails techniques (comportement du compilateur Rust, outillage Python), mais les idées, les chiffres et les opinions restent les miens, et j’ai relu chaque ligne avant publication.