Aller au contenu

Go vers Rust : migration utile ou effet de mode ?

·

Go et Rust sont souvent présentés comme deux langages rivaux du développement système moderne. L’un vise la simplicité et la productivité des équipes ; l’autre pousse la performance et la sécurité mémoire jusqu’à leurs limites logiques. En pratique, ils ne jouent pas tout à fait dans la même cour — mais ils se croisent régulièrement sur des projets de services backend, d’outils CLI ou d’APIs à haute fréquence.

La question « devrait-on migrer notre base Go vers Rust ? » revient de plus en plus souvent. Elle a récemment attiré plus de 250 commentaires sur Hacker News, ce qui n’arrive pas pour n’importe quel sujet technique. C’est le signe que la question est sérieuse, et que la réponse n’est pas triviale.

Spoiler : non, tu ne devrais probablement pas tout réécrire. Mais il y a des cas précis où ça a du sens, et comprendre lesquels te permettra de prendre la bonne décision plutôt que de suivre l’air du temps.

TL;DR — Rust est plus rapide et plus sûr que Go en termes de gestion mémoire, mais son borrow checker impose une courbe d’apprentissage sérieuse. Une migration complète est rarement justifiée ; une migration ciblée sur les modules critiques, oui. Avant de tout réécrire, assure-toi d’avoir un vrai problème de performance ou de sécurité mémoire à résoudre.

Pourquoi les équipes Go regardent vers Rust

Go a gagné sa place dans l’écosystème backend grâce à sa simplicité, ses goroutines et son tooling impeccable. Mais certaines limites reviennent régulièrement sur la table. La première : le garbage collector. Pour des applications à faible latence — trading algorithmique, traitement temps réel, télécoms — les pauses GC restent un problème. Un p99 qui spike de 2 ms à 20 ms à cause d’un cycle de GC, c’est inacceptable dans certains contextes métier.

La deuxième limite, c’est le plafond de performance brute. Go est rapide, mais pas aussi rapide que du code Rust bien écrit. Les benchmarks montrent régulièrement un avantage de 20 à 40 % en faveur de Rust sur des workloads CPU-bound. Ce n’est pas universellement significatif, mais quand tu fais tourner des milliers d’instances en cloud, ça se traduit directement en coûts d’infrastructure qui font mal à la fin du mois.

Il y a aussi la question du système de types. Go a longtemps manqué de generics — ils sont arrivés en 1.18, en 2022, mais le support reste limité comparé à ce qu’offre Rust. Les développeurs habitués à des abstractions de haut niveau ont parfois le sentiment de se battre contre le langage pour exprimer certains patterns, et finissent par dupliquer du code que Rust aurait factorisé proprement.

Ce que Rust apporte concrètement

Rust, c’est avant tout la promesse de la sécurité mémoire sans garbage collector. Le compilateur garantit l’absence de dangling pointers, de race conditions et de use-after-free à la compilation — pas à l’exécution. C’est un claim fort, et il tient : la majorité des CVE liés à la mémoire dans des projets C et C++ n’existeraient tout simplement pas en Rust.

Les « zero-cost abstractions » sont l’autre argument phare. Tu peux écrire du code de haut niveau — iterators, closures, types génériques — et le compilateur produit un binaire aussi optimal que si tu avais tout écrit à la main. En pratique, c’est rarement aussi absolu, mais Rust reste impressionnant sur ce point, notamment pour du traitement de données intensif ou du parsing à grande échelle.

Pour des cas d’usage comme les outils CLI, les moteurs de traitement de données, les services système ou les modules WebAssembly, Rust a prouvé sa valeur. Des projets comme Ripgrep, Starship ou Deno ont mis ça en lumière. Et quand tu construis une application sur mesure à haute performance, le choix du langage peut faire une vraie différence sur la durée de vie du produit.

DÉCOUVREZ NOS RÉALISATIONS

Capture d'écran du site Direct Automobiles réalisé par Westcode

Direct Automobiles

Site vitrine WordPress pour un concessionnaire automobile a Louverne (53). Design premium, photographie et formulaire de contact.

Voir le projet →

Le borrow checker — ton meilleur ennemi

Si tu viens de Go, le plus gros choc culturel avec Rust, c’est le borrow checker. En Go, tu passes des pointeurs sans trop t’en préoccuper. En Rust, chaque valeur a un propriétaire unique, peut être empruntée de façon immutable ou mutable — jamais les deux à la fois — et le compilateur vérifie tout ça à la compilation. Au début, tu vas passer beaucoup de temps à te battre contre lui.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 est moved, il n'existe plus
    println!("{}", s1); // Erreur de compilation : value borrowed after move
}

Ce pattern serait parfaitement valide en Go, mais ne compile pas en Rust. La valeur a été « moved » — transférée à s2. Pour utiliser s1 ensuite, il faudrait cloner ou utiliser une référence. C’est déroutant au début, mais c’est exactement ce mécanisme qui élimine les bugs mémoire avant que le programme tourne.

Les lifetimes sont l’autre concept qui déroute les nouveaux arrivants. Elles permettent au compilateur de vérifier que les références restent valides le temps nécessaire. Bonne nouvelle : dans la plupart du code applicatif, le compilateur les infère tout seul. Mauvaise nouvelle : quand tu dois les annoter explicitement, c’est souvent le signe que ton design a un vrai problème qu’il vaut mieux traiter maintenant plutôt qu’à la prochaine mise en prod.

⚠️ Plan réaliste : pour un développeur Go senior, compte 2 à 3 mois avant d’être vraiment productif en Rust. Pour quelqu’un qui sort d’un langage avec GC (JavaScript, Python), c’est plutôt 4 à 6 mois. Ce n’est pas une raison de ne pas apprendre — c’est une raison de ne pas promettre une migration en Q1.

Migrer progressivement plutôt que tout réécrire d’un coup

La leçon numéro un des migrations réussies : ne jamais réécrire de zéro en une fois. C’est vrai pour les migrations de frameworks, de bases de données, et c’est vrai ici aussi. La migration Go → Rust se fait mieux module par module, en commençant par les parties les plus critiques en termes de performance ou de sécurité, et en gardant le reste en Go le temps de valider l’approche.

Rust offre une interopérabilité avec le C via FFI (Foreign Function Interface), et Go peut appeler du C via cgo. En pratique, tu peux exposer un module Rust en bibliothèque dynamique et l’appeler depuis ton code Go existant. C’est complexe à mettre en place, mais ça permet de remplacer progressivement les parties chaudes sans bloquer le reste de l’équipe ni refondre l’architecture globale.

L’outillage Rust facilite ce travail. Cargo, le gestionnaire de paquets et de build, est régulièrement cité comme l’un des meilleurs dans n’importe quel écosystème. cargo test, cargo bench, cargo clippy (le linter) et cargo fmt sont cohérents, bien documentés et fonctionnent dès le départ. Si tu viens de Go modules, tu vas trouver Cargo plus structurant — dans le bon sens du terme.

Ce que Go fait toujours mieux

Soyons honnêtes : Go reste supérieur à Rust dans plusieurs domaines. Le premier, c’est la courbe d’apprentissage. Onboarder un développeur backend sur Go prend quelques semaines. Sur Rust, compte plusieurs mois avant d’être vraiment autonome. Pour une petite équipe qui doit itérer vite, c’est un argument souvent décisif, et il a le mérite d’être mesurable.

WESTCODE BY LEB

Studio web en Mayenne — On crée des outils numériques qui transforment

La concurrence est aussi une force de Go. Les goroutines sont légères, simples à utiliser, et le scheduler est très bien optimisé. Rust propose async/await avec des runtimes comme Tokio — puissant, mais l’écosystème async Rust reste plus complexe à appréhender et à déboguer. Si ton principal besoin est de gérer des milliers de connexions concurrentes, Go reste probablement le meilleur choix, et de loin.

Les temps de compilation méritent aussi d’être mentionnés : Rust compile lentement. Sur un gros projet, les builds peuvent prendre plusieurs minutes et créer une friction réelle dans la boucle de développement. Go est réputé pour sa rapidité de compilation — c’est une différence qui se ressent au quotidien et qui, cumulée à la complexité du borrow checker, peut peser dans la balance.

Changer de langage parce que « Rust est plus safe » sans avoir de problème de sécurité mémoire concret, c’est de l’ingénierie inutile.

La migration n’est pas une fin en soi

Mon avis tranché : la migration Go → Rust est justifiée dans un nombre de cas beaucoup plus restreint qu’on ne le lit en ligne. Si ton application Go tourne bien, si tes temps de réponse sont acceptables, si ton équipe est productive et que tu n’as pas de bug mémoire en prod, tu n’as rien à migrer. La pression sociale du « Rust c’est l’avenir » ne justifie pas six mois de refonte.

En revanche, si tu développes un outil CLI distribué à grande échelle, un moteur de parsing intensif, un module cryptographique, ou du code embarqué, Rust est un excellent choix — parfois le seul qui tient vraiment ses promesses sur la durée. Des entreprises comme Discord, Amazon ou Microsoft ont intégré Rust dans leur stack pour exactement ces raisons, avec des gains mesurables à la clé.

✅ La bonne question à se poser avant toute migration : « Quel problème concret Rust résout-il que Go ne peut pas résoudre ? » Si tu ne trouves pas de réponse précise en moins de deux minutes, reste sur Go.

Ce qui est sûr, c’est que Rust va continuer de progresser. Le langage gagne en maturité, son écosystème s’enrichit, et les outils pour apprendre s’améliorent. La vraie question n’est pas « Go ou Rust ? » mais « est-ce que mon problème vaut le coût de Rust ? » Prends le temps de répondre honnêtement à cette question avant d’ouvrir un dépôt vide et de tout réécrire. Et si tu as un doute sur l’architecture à adopter pour un projet backend critique, c’est exactement le genre de contexte où un accompagnement sur mesure peut t’éviter six mois d’erreurs évitables.