1 points par GN⁺ 3 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Le passage de Go à Rust relève moins d’un choix motivé par le gain de vitesse que d’un déplacement des problèmes de nil, de gestion des erreurs, de data races et de durée de vie des ressources vers des garanties à la compilation
  • Go a pour atouts une compilation rapide, des goroutines simples et un solide écosystème backend, mais Rust empêche davantage d’erreurs dès le système de types avec Option, Result et Send/Sync
  • Le borrow checker de Rust et async/await entraînent un coût en courbe d’apprentissage et en ergonomie, et le temps de compilation doit clairement être considéré comme un recul par rapport à Go
  • Pour la transition, il est plus pertinent de commencer par des composants aux frontières nettes — comme des services hot path, des workers ou certains endpoints derrière une gateway — plutôt que par une réécriture complète
  • Les bénéfices attendus se résument à une baisse de 20 à 60 % du CPU, de 30 à 50 % de la mémoire, une latence P99 plus stable, ainsi qu’une réduction des pannes liées aux déréférencements de nil et aux conditions de concurrence

Le point central de la transition

  • Le passage de Go à Rust porte moins sur la question de savoir si « Rust est plus rapide » que sur les garanties de correction, les compromis à l’exécution et les différences d’expérience développeur
  • La comparaison se concentre sur les services backend, avec comme base les points forts de Go : petits binaires statiques, bibliothèque standard orientée réseau, et écosystème HTTP server, gRPC et base de données
  • Certains éléments peuvent aussi s’appliquer aux outils CLI, aux firmwares embarqués ou aux moteurs de jeu, mais ce ne sont pas les cibles optimisées ici
  • Comme documents de contexte, sont cités “Go vs Rust? Choose Go.” de 2017 et “Rust vs Go: A Hands-On Comparison” de l’équipe Shuttle
  • Go est un langage à succès, mais certains choix de conception — comme l’usage généralisé de nil, une gestion des erreurs reposant sur la discipline plutôt que sur le type système, ou l’absence longtemps prolongée de génériques — deviennent des points de débat majeurs face à Rust
  • Dans la JetBrains Developer Ecosystem Survey, Go est présenté comme un langage qui maintient une part de 17 à 19 % des développeurs actifs, tandis que Rust continue de croître mais reste à un niveau plus modeste

L’outillage

  • Go et Rust disposent tous deux d’un outillage batteries included offrant, via une interface cohérente, build, tests, formatage, linting et gestion des dépendances
  • cargo fournit plus largement, comme outillage de premier plan, les fonctions correspondant aux outils de Go
    • go.mod / go.sumCargo.toml / Cargo.lock : configuration du projet et manifeste des dépendances
    • go get / go mod tidycargo add / cargo update : ajout et résolution des dépendances
    • go buildcargo build : compilation
    • go run .cargo run : exécution après build
    • go test ./...cargo test : tests
    • go vet ./...cargo clippy : linter, Clippy étant bien plus prescriptif que vet
    • gofmt / goimportscargo fmt : formateur automatique sans configuration
    • golangci-lint runcargo clippy -- -D warnings : mode de lint strict
    • go doccargo doc --open : génération et consultation de la documentation API
    • pprofcargo flamegraph / samply : profiling CPU
    • govulncheckcargo audit : vérification des vulnérabilités à partir d’une base de données d’avis de sécurité
  • Dans Go, il est fréquent de combler les manques avec des outils tiers comme golangci-lint, mockgen, air ou goreleaser, alors que Rust couvre davantage de besoins de base via son écosystème principal
  • Même lorsqu’un crate externe est nécessaire, l’installation se fait en une fois avec cargo install cargo-nextest, comme pour cargo watch ou cargo nextest, et l’outil se comporte ensuite comme un outil natif, par exemple cargo nextest
  • gofmt et rustfmt ont surtout l’avantage de faire disparaître les débats de style en revue de code, plus que de satisfaire des préférences fines de mise en forme
    • Citation des Go Proverbs de Rob Pike : “Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.”

Différences fondamentales entre Go et Rust

  • Les deux langages sont compilés, à typage statique, déployables sous forme de binaire unique et dotés d’un modèle de concurrence solide, mais ils diffèrent par l’étendue des garanties fournies par le compilateur et par le niveau de contrôle sur le comportement à l’exécution
  • Les principaux points de comparaison sont les suivants
    • Première version stable : Go en 2012, Rust en 2015
    • Système de types : Go est statique et structurel, avec prise en charge des génériques depuis la 1.18 ; Rust est statique et nominal, avec génériques, traits et durées de vie
    • Gestion mémoire : Go utilise un garbage collector concurrent à faible latence ; Rust repose sur la propriété et l’emprunt, sans GC
    • Sûreté vis-à-vis du nul : nil est omniprésent en Go ; Rust n’a pas de nul et Option<T> en est le substitut au niveau du type
    • Gestion des erreurs : Go utilise l’interface error et if err != nil { ... } ; Rust utilise Result<T, E>, l’opérateur ? et un pattern matching complet
    • Concurrence : Go repose sur les goroutines et des channels de type CSP ; Rust sur async/await avec tokio, ainsi que des channels et des threads
    • Annulation : Go utilise context.Context par convention ; Rust emploie des transmissions explicites et vérifiées par le type, comme CancellationToken
    • Data races : Go les détecte de manière probabiliste à l’exécution avec -race ; Rust les détecte à la compilation avec Send/Sync
    • Temps de compilation : Go est très rapide, tandis que Rust est lent, surtout sur les builds propres
    • Runtime : Go embarque un runtime d’environ 2 Mo avec GC ; Rust n’a pas de runtime en dehors de libc, ou peut être construit en statique complet avec MUSL
    • Taille de l’écosystème : Go compte environ 750 000+ modules, Rust 250 000+ crates
  • En Rust, des vérifications comme la gestion de nil, la propagation des erreurs, les data races, la durée de vie des ressources, l’annulation ou les génériques — qui en Go reposaient davantage sur les conventions, les outils et la détection à l’exécution — sont intégrées au système de types
  • Le Mutex<T> de Rust n’autorise l’accès à la valeur interne qu’au travers du garde obtenu par .lock(), ce qui supprime du type même toute « voie où l’on oublierait de verrouiller »
  • Le même schéma se retrouve avec Option, Result, &mut T, Send/Sync et les gardes RAII ; une fois l’habitude prise, le compilateur remplace une partie des vérifications mentales du développeur

Les limites de Go qui poussent à envisager Rust

  • Comme Go est suffisamment rapide pour la plupart des charges backend, la principale raison d’évaluer Rust tient moins à la vitesse qu’à la lourdeur du traitement des erreurs, au risque de pointeurs nil et à l’absence de fonctionnalités avancées du système de types comme les enum et les traits
  • Les interfaces Go ne remplacent pas suffisamment les traits de Rust, et la bibliothèque standard n’inclut pas de type Set, ce qui impose des contournements idiomatiques comme map[T]struct{}
  • Les paniques nil en production

    • Un service Go peut fonctionner normalement pendant des mois puis déclencher une panique de goroutine sur un chemin de code précis à cause d’une vérification oubliée d’un pointeur nil
    • Dans l’exemple, Find renvoie (*User, error) et, en cas de « not found », error vaut nil mais la vérification de user reste à la charge de l’appelant
    • user.Account.Notify() peut planter si user ou Account vaut nil
    • Des linters comme nilaway et staticcheck, ainsi que les vérifications de l’IDE, en détectent une partie, mais ils sont opt-in, probabilistes et franchissent mal les frontières entre packages de façon fiable
    • Le Option<T> de Rust empêche tout déréférencement sans traitement du cas None, ce qui élimine cette catégorie d’incidents
  • Les data races que -race n’a pas détectées

    • go test -race est un excellent outil, mais comme il s’agit d’un détecteur à l’exécution, il ne trouve que les races réellement déclenchées pendant les tests
    • En Go, un code où deux goroutines modifient une map sans verrou compile quand même, puis peut exploser en production sous charge
    • En Rust, le partage d’état mutable entre threads exige des types implémentant Send et Sync, et tenter de partager une simple HashMap entre threads ne compile pas
    • Cela force l’usage de Arc<Mutex<...>>, Arc<RwLock<...>> ou de canaux, et les race conditions deviennent des erreurs de type
    • Paul Dix a cité explicitement l’élimination des data races comme motivation de la réécriture d’InfluxDB 3.0
      • « [The main benefit is] fearless concurrency — eliminating data races essentially, which we had before. Really gnarly bugs in version 1 of Influx due to that. »
      • Source : Paul Dix, Founder & CTO, InfluxData, Rust in Production
  • Un traitement des erreurs composable

    • En Go, if err != nil { return err } peut diluer la logique réelle d’une fonction, et le fait d’ajouter du contexte avec fmt.Errorf("doing X: %w", err) repose sur la discipline plutôt que sur une règle imposée par le compilateur
    • Dans un thread Lobste.rs, des développeurs Go expérimentés rétorquent que errcheck et golangci-lint détectent la plupart des oublis de traitement d’erreur, et que le if err != nil explicite est plus lisible qu’une chaîne dense de ?
    • Peter Bourgon présente le traitement explicite des erreurs en Go comme une valeur culturelle intentionnelle
      • « I think that error handling should be explicit, this should be a core value of the language. »
      • Source : Peter Bourgon, GoTime #91, cité dans le Zen of Go de Dave Cheney
    • Le Result<T, E> de Rust fait partie de la signature de type elle-même, donc il est impossible de l’oublier, et des enum définies avec thiserror::Error et #[from] permettent d’obtenir la conversion d’erreurs et la vérification d’exhaustivité
    • Lorsqu’on ajoute un nouveau variant d’erreur, le compilateur indique quels match doivent être mis à jour
  • Des génériques sans boxing

    • Les génériques de Go 1.18 sont utiles, mais présentent des limites comme l’absence de méthodes avec paramètres de type, le GC shape stenciling et des caractéristiques de performance parfois surprenantes
    • Les génériques de Rust sont monomorphisés : chaque instanciation produit un code spécialisé, sans coût à l’exécution
    • Combinés aux traits, ils permettent des abstractions à coût nul
    • C’est plus important dans l’infrastructure partagée — middleware, generic repository, decoder, parser — que dans le code des handlers, et Go revient souvent à interface{}/any et aux assertions de type dans ces zones
  • Une latence prévisible

    • Le GC de Go est excellent, concurrent, à faible pause et bien optimisé pour les charges de service classiques, mais « low-pause » ne veut pas dire « no-pause »
    • Dans les situations avec beaucoup d’allocations, la queue de latence P99 peut être moins bonne qu’avec une implémentation Rust qui n’alloue pas sur le hot path
    • Dans les systèmes sensibles à la latence — trading, enchères en temps réel, proxy réseau, collecte à très haut débit — l’absence de pauses GC constitue un avantage réel
    • Stephen Blum explique que Rust était nécessaire pour obtenir, à l’échelle de PubNub, la capacité de performance par dollar dont ils avaient besoin
      • « Go is great at our scale, but we really need something that is going to give us the price-per-dollar performance capacity that we need, and Rust is going to get us there. That’s why basically everything is heading towards Rust these days. »
      • Source : Stephen Blum, CTO, PubNub, Rust in Production

Correspondances Rust des patterns Go

  • Le moyen le plus rapide de se familiariser avec Rust consiste à faire correspondre les patterns Go que l’on connaît déjà à leurs équivalents en Rust
  • Un exemple plus long implémentant le même service backend dans les deux langages est disponible dans Shuttle comparison
  • Gestion des erreurs : if err != nil vs Result<T, E>

    • En Go, après os.ReadFile(path) et json.Unmarshal, on renvoie une erreur enrichie de contexte via if err != nil
    • En Rust, on utilise fs::read_to_string(path)?, serde_json::from_str(&data)?, puis Ok(cfg)
    • L’opérateur ? remplace le pattern if err != nil { return err } et gère aussi la conversion de type si From<E1> for E2 est implémenté
    • Le #[from] de thiserror prend en charge cette conversion de manière idiomatique
  • Null : nil vs Option<T>

    • En Go, GetUser(id string) *User renvoie nil si l’utilisateur est introuvable, et si l’appelant fait fmt.Println(u.Name), cela provoque un panic lorsque la valeur est nil
    • En Rust, get_user(id: &str) -> Option<User> renvoie Some(User) ou None
    • let user = get_user("123"); println!("{}", user.name); produit une erreur de compilation, car user est un Option<User> et non un User
    • Il faut traiter à la fois Some(u) et None avec match get_user("123")
    • En Rust sûr, il n’existe pas de nil, et une référence ne peut pas être nulle
  • Interfaces vs traits

    • Les interfaces Go sont structurelles, et un type satisfait implicitement une interface
    • Les traits Rust sont nominatifs et doivent être implémentés explicitement
    • L’approche Go est bien adaptée au duck typing improvisé, tandis que l’approche Rust facilite le refactoring et la discoverability, et permet de retrouver avec grep les implémentations d’un trait donné
    • Les fonctions génériques avec trait bound, comme fn handle<R: Reader>(r: R), couvrent la plupart des cas et, grâce à la monomorphisation, évitent le dispatch runtime
    • Pour stocker des implémentations hétérogènes nécessitant un dispatch runtime, on utilise Box<dyn Trait> ou Arc<dyn Trait>
  • Goroutine vs tâche async

    • Le modèle de concurrence de Go est simple, comme avec go doWork(ctx, input) ; les goroutines sont légères et le runtime les ordonnance au-dessus des threads OS
    • L’un des grands atouts de Go est qu’il n’existe pas de distinction syntaxique entre code séquentiel et code parallèle
    • En Rust, pour les services backend, on utilise presque toujours async/await sur l’executor tokio
    • Les fonctions async renvoient un Future et ne s’exécutent pas tant qu’elles ne sont pas await ou spawn
    • Le compilateur suit les Send/Sync avant et après les points .await, et signale une erreur de compilation si une valeur non-Send est conservée au-delà d’un await
    • Comme il n’y a pas de préemption intégrée de type goroutine, faire tourner longtemps une tâche CPU-bound dans une tâche async peut affamer l’executor ; il faut la basculer vers tokio::task::spawn_blocking ou rayon
  • context.Context vs CancellationToken

    • En Go, on transmet context.Context à tous les appels bloquants
    • Rust n’a pas de context.Context intégré, et l’équivalent le plus proche pour l’annulation est tokio_util::sync::CancellationToken
    • Les timeouts s’appliquent en enveloppant un future avec tokio::time::timeout(dur, fut)
    • Les deadlines et les valeurs sont souvent transmises via des arguments explicites ou un span tracing, plutôt qu’au moyen d’un unique objet context
    • Citation de Dave Cheney dans The Zen of Go :
      • « Go n’a pas de moyen d’ordonner à une goroutine de s’arrêter. Il n’existe pas de fonction stop ou kill, et c’est une bonne chose. Si nous ne pouvons pas ordonner à une goroutine de s’arrêter, nous devons plutôt le lui demander, poliment. »
    • En Go, cette « demande polie » est conventionnellement transmise via context.Context ; en Rust, ce sera CancellationToken ou un canal watch, mais le compilateur peut signaler les oublis
  • Chaînes : string vs String et &str

    • En Go, string est une slice d’octets UTF-8 ; lors d’une affectation, son en-tête est copié et les octets sous-jacents immuables sont partagés
    • Rust sépare cela en deux types
      • String : possède les données, est alloué sur le heap et extensible
      • &str : vue empruntée sur d’autres données de chaîne, qui correspond dans la plupart des cas à un paramètre string en Go
    • La règle empirique consiste à accepter &str en argument et à renvoyer String lorsqu’on crée de nouvelles données
    • La séparation entre &str et String illustre en version condensée le modèle Rust « emprunter vs posséder »

Évaluation des génériques en Go

  • Go a introduit les génériques avec la version 1.18 en mars 2022, soit 13 ans après le lancement du langage
  • Les génériques sont utiles, mais sont jugés incapables d’apporter pleinement les avantages attendus en Rust, Haskell ou C++ moderne, tout en cumulant une bonne partie des inconvénients des systèmes de types génériques
  • La bibliothèque standard les utilise très peu

    • Même 3 ans après l’introduction des génériques, la bibliothèque standard de Go les évite encore dans la plupart des cas
    • sort.Slice accepte toujours une closure func(i, j int) bool au lieu d’une contrainte cmp.Ordered
    • sync.Map reste typé en any/any
    • Les helpers génériques existants se limitent à quelques packages, comme certains éléments sous slices, maps, cmp et sync
    • La promesse de compatibilité Go 1 explique en partie la difficulté à remanier les API non génériques existantes, mais Go n’utilise pas les génériques comme outil principal à la manière de Rust
    • En Rust, les génériques irriguent le langage depuis le début, avec Option<T>, Result<T, E>, Vec<T>, HashMap<K, V>, Iterator, From/Into, ainsi que toutes les collections et les smart pointers
  • Pas de système de traits, uniquement des contraintes structurelles

    • En Rust, les génériques sont liés aux traits, qui prennent en charge le polymorphisme ad hoc, les supertraits, les types associés, les blanket impl et la coherence
    • Les contraintes de Go sont plus proches d’interfaces enrichies de l’opérateur ~ pour l’appartenance à un ensemble de types
    • Go n’a ni hiérarchie de supertraits comme trait Ord: Eq + PartialOrd en Rust, ni types associés comme type Item; dans Iterator, ni blanket impl comme impl<T: Display> ToString for T
    • Go ne permet pas d’utiliser des méthodes avec paramètres de type, ce qui rend impossible une forme comme func (s Set[T]) Map[U](<https://corrode.dev/learn/migration-guides/go-to-rust/f func(T>) U) Set[U]
    • Dès que l’abstraction dépasse le cadre de « fonctions qui opèrent sur un T arbitraire doté de quelques opérations », Go revient à any, aux assertions de type, à la génération de code et à la réflexion à l’exécution
  • Différences de stratégie d’inférence de types et d’implémentation

    • Rust propage les informations de type dans l’ensemble de l’expression, y compris les closures, les chaînes d’itérateurs et l’opérateur ?
    • L’inférence de Go est plus limitée : elle déduit généralement les paramètres de type à partir des arguments de fonction, mais ne peut pas les inférer à partir du contexte de retour et exige souvent des arguments de type explicites au point d’appel
    • Go a choisi une voie intermédiaire avec les GCShape stenciling and dictionaries, ce qui préserve des temps de compilation rapides, mais peut introduire une indirection à chaque appel de méthode sur un paramètre de type
    • L’article de PlanetScale est cité pour illustrer ce point
    • Rust produit du code machine spécialisé pour Vec<i32> et Vec<String> respectivement, sans dispatch dynamique à l’exécution
    • Le coût de la monomorphisation se paie en temps de compilation, et les deux langages optimisent des objectifs différents
  • Ne comble pas les trous du système de types

    • En Rust, les génériques et les traits éliminent la plupart des situations qui exigeraient Box<dyn Any> ou de la réflexion à l’exécution
    • Les génériques de Go n’éliminent ni any, ni reflect, ni les schémas dominants de génération de code dans les ORM, les décodeurs ou les mocks
    • encoding/json utilise toujours la réflexion, database/sql utilise toujours any, et mockgen génère toujours du code
    • Les génériques de Go donnent l’impression d’un nouvel outil utile dans des cas restreints, tandis qu’en Rust ils jouent un rôle fondamental, au point que le langage s’effondrerait sans eux

Écosystème backend Rust

  • L’écosystème Rust converge lui aussi dans une certaine mesure vers des « choix par défaut » pour les services backend classiques
  • Correspondances représentatives :
    • Serveur HTTP : Go net/http, chi, gin, echo, fiber → Rust axum sur hyper
    • Client HTTP : Go net/http, resty → Rust reqwest
    • gRPC : Go google.golang.org/grpc + protoc-gen-go → Rust tonic + prost
    • SQL : Go database/sql, sqlc, sqlx, gorm → Rust sqlx, sea-orm, diesel
    • Migrations : Go golang-migrate, goose → Rust sqlx migrate, refinery
    • JSON : Go encoding/json, sonic, goccy/go-json → Rust serde + serde_json
    • Logging : Go log/slog, zerolog, zap → Rust tracing + tracing-subscriber
    • Métriques : Go prometheus/client_golang → Rust metrics + metrics-exporter-prometheus
    • Configuration : Go viper, koanf → Rust config / config-rs, figment
    • CLI : Go cobra, urfave/cli → Rust clap derive
    • Erreurs : Go errors, pkg/errors → Rust thiserror pour les bibliothèques, anyhow pour les binaires
    • Tests : Go testing, testify, gomega → Rust #[test] intégré, rstest, assert_matches
    • Mocking : Go mockgen, moq → en Rust, les fakes écrits à la main sont idiomatiques, mais mockall est aussi utilisé
    • Tâches en arrière-plan : Go goroutines + errgroup → Rust tokio::spawn + JoinSet
  • Pour un service backend typique, la combinaison axum + sqlx + tokio + tracing + serde + clap est présentée comme couvrant 90 % des besoins

Le borrow checker et la courbe d’apprentissage

  • Il faut partir du principe qu’en passant de Go à Rust, on va se heurter à un mur
  • Le runtime de Go gère à votre place la mémoire et l’aliasing, mais Rust déplace ces décisions dans le système de types, si bien que pendant les premières semaines, du code qui « devrait évidemment fonctionner » peut être refusé par le compilateur
  • Schémas auxquels les développeurs Go se heurtent souvent :
    • Références de longue durée de vie : en Go, conserver longtemps un *User extrait d’une map est naturel, mais en Rust, modifier la map est bloqué tant que cet emprunt reste vivant
    • Struct auto-référencées : en Go, on peut mettre des données et un itérateur sur ces données dans la même struct, mais en Rust, cela demande Pin, ouroboros ou une refonte
    • Partage d’état mutable entre goroutines : le schéma Go mu sync.Mutex; data map[K]V devient en Rust quelque chose comme Arc<Mutex<HashMap<K, V>>>
    • Retourner une référence depuis une fonction : les annotations de durée de vie entrent en jeu, un concept nouveau pour les développeurs Go
  • Il faut voir le borrow checker non pas comme un « gardien » gênant, mais comme un mécanisme qui révèle de vrais bugs
  • Il élimine à la compilation les cas où une valeur est réutilisée après avoir été déplacée, où plusieurs threads touchent simultanément les mêmes données, où des pointeurs null ou pendants sont déréférencés, ou encore où une référence vit plus longtemps que la valeur
  • Une fois le concept d’emprunt intériorisé, il cesse d’être un adversaire pour devenir un partenaire, et les développeurs Rust expérimentés disent en général qu’entre 4 et 12 semaines, le borrow checker devient une aide précieuse
  • Le CTO de PubNub, Stephen Blum, a déclaré dans Rustacean Station que le premier mois lui donnait « l’impression de réapprendre à programmer », car il avait été forcé d’affronter le borrow checker et les durées de vie
  • Ed Page, mainteneur de clap, explique dans Rustacean Station: clap with Ed Page que le borrow checker lui a permis de se concentrer sur les problèmes de plus haut niveau et qu’il détectait aussi des points qui lui avaient échappé dans son analyse

Principales difficultés d’une transition vers Rust

  • Temps de compilation

    • Il faut considérer les temps de compilation de Rust comme une régression nette par rapport à Go, car un build release propre d’un service de taille intermédiaire peut prendre plusieurs minutes, là où Go compile presque instantanément
    • Les builds incrémentaux et cargo check restent raisonnables, et les temps de compilation se sont améliorés d’année en année, mais l’écart avec Go se ressent
    • Dans la boucle d’édition, utilisez cargo check, découpez en workspace quand cela devient utile, et gardez les crates riches en macros procédurales dans des crates séparées afin qu’elles ne soient recompilées qu’en cas de modification
    • Pour aller plus loin, on peut consulter des conseils pour réduire les temps de compilation de Rust
  • Le problème de la coloration asynchrone

    • La séparation entre async fn et fn en Rust est l’une des plus grosses régressions d’ergonomie lorsqu’on vient de Go
    • Les async trait sont stables depuis Rust 1.75, mais présentent encore des aspérités lorsqu’on les mélange au dispatch dynamique
    • Dans certains cas, on finit par utiliser la crate async-trait pour lisser ces problèmes
  • Un écosystème plus petit

    • L’écosystème de crates Rust est en croissance et la qualité des bibliothèques est globalement élevée, mais Go reste en avance dans certains domaines proches du backend
    • Les domaines où Go garde l’avantage incluent les opérateurs Kubernetes, les SDK des fournisseurs cloud et les drivers de base de données pour certains stockages de niche
    • Avant d’acter une migration, il faut consacrer environ une journée à vérifier qu’il existe des alternatives Rust viables pour les bibliothèques dont vous dépendez
    • Certaines équipes peuvent devoir mettre à jour une crate de validation de schéma XML abandonnée ou écrire elles-mêmes un client pour un protocole peu répandu

Stratégies d’intégration

  • Une transition réussie de Go vers Rust relève moins d’une réécriture totale en une seule fois que de choix tactiques
  • Victor Ciura, Principal Engineer chez Microsoft, explique dans Rust in Production qu’il s’agit non pas de « tout réécrire en Rust pour le plaisir », mais d’un choix tactique consistant à utiliser Rust lorsqu’un nouveau composant s’y prête mieux
  • 1. Extraire le hot path en service distinct

    • Si un service donné pose continuellement problème, la migration la moins risquée consiste à ne réécrire en Rust que ce service, derrière le même contrat d’API
    • La cible peut être un service gourmand en CPU, sensible à la latence ou sujet à des problèmes de stabilité récurrents
    • Les autres services Go continuent de communiquer en HTTP/gRPC et n’ont donc pas besoin de connaître le langage d’implémentation interne
    • Jeff Kao, CTO de Radar, déclare dans Rust in Production que l’article de Discord sur son passage de Go à Rust a donné à Radar l’idée de tenter la même approche
  • 2. Remplacer des sidecars ou des processus worker

    • Les workers d’arrière-plan, consommateurs de files, pipelines d’ingestion et traitements batch CPU-bound constituent de bonnes premières cibles
    • Ils disposent généralement de frontières d’entrée/sortie claires, comme une queue ou un topic, et ne partagent pas d’état in-process avec le reste du système
  • 3. cgo est possible, mais pénible

    • Il est possible d’appeler Rust depuis Go via cgo, et il existe même un bon guide sur le sujet
    • En backend, ce n’est généralement pas recommandé
    • La complexité du build et le surcoût FFI compensent souvent les avantages par rapport à une approche consistant à « monter un service Rust et le placer derrière un appel réseau »
    • Cela peut être plus pratique pour des bibliothèques et des outils CLI
  • 4. Appliquer le Strangler Pattern derrière une gateway

    • Si vous avez une API gateway ou un reverse proxy, vous pouvez ne router que certains endpoints vers un nouveau service Rust et laisser le reste en Go
    • Cela fonctionne particulièrement bien lorsqu’un seul bounded context, comme l’authentification, la recherche ou le paiement, se prête à servir d’unité de migration
    • Ce schéma est appelé « strangler fig », car le nouveau service grandit autour du service existant jusqu’à le remplacer complètement

Conseils de migration sur le terrain

  • Il faut commencer par un service aux frontières claires, et non par le service le plus central ou le plus fréquemment déployé
  • Choisissez un service dont le contrat avec le reste du système est bien défini et dont le rayon d’impact est limité
  • Garder le même contrat d’API

    • Si le service Go expose une API REST, le service Rust doit conserver les mêmes routes, le même format JSON et les mêmes wrappers d’erreur
    • La migration est invisible pour les clients, et vous pouvez faire basculer progressivement le trafic via la gateway
  • Ne pas transposer littéralement les idiomes

    • if err != nil { return err } devient ?
    • Le schéma « une goroutine par requête » ne se traduit en tokio::spawn que lorsqu’il est réellement nécessaire
    • axum traite déjà les requêtes en parallèle
    • Les interfaces à une seule méthode deviennent généralement des trait bounds génériques plutôt que des Box<dyn Trait>
  • Utiliser le compilateur comme pair programmer

    • Les messages d’erreur du compilateur Rust sont en général de grande qualité, et si on les lit lentement, ils indiquent presque toujours la bonne réponse
    • Les membres d’équipe qui souffrent le plus longtemps sont souvent ceux qui ne considèrent pas le compilateur comme un collaborateur, mais comme un adversaire
  • Investir tôt dans la formation

    • Les migrations Rust menées « à côté » du reste se terminent souvent mal
    • Il faut réellement dégager du temps d’apprentissage, via des ateliers, des cours en ligne ou des sessions de pair programming sur du vrai code
    • Une fois l’équipe compétente, l’investissement initial est récupéré plusieurs fois

Domaines où Go reste pertinent

  • Il n’est pas nécessaire de tout migrer vers Rust, et il existe des domaines où Go est particulièrement adapté
  • Outils natifs Kubernetes

    • Pour les opérateurs, contrôleurs et CRD, l’écosystème reste très largement centré sur Go
  • Utilitaires CLI et outils de développement

    • Leurs points forts sont une compilation rapide, une cross-compilation simple et un déploiement facile
  • Services de glue

    • Pour une fine couche d’API, des proxys ou des convertisseurs de format, la part de boilerplate en Rust ne vaut pas toujours l’effort
  • Là où la vitesse de l’équipe prime sur les garanties absolues de correction

    • Dans les domaines où il faut avancer vite, Go peut continuer à être le bon choix
    • Jon Seager, VP of Engineering chez Canonical, explique dans Rust in Production que Go est un excellent choix pour les services réseau, que Canonical utilise beaucoup Go et que Juju est lui aussi une énorme codebase Go
    • Une stratégie hybride est courante, et beaucoup d’équipes finissent avec un backend polyglotte : Go pour les services « ennuyeux », Rust pour ceux où la stabilité et les performances compensent l’effort supplémentaire

Améliorations attendues

  • Les chiffres varient fortement selon la charge de travail : il faut donc les voir comme un ordre de grandeur, pas comme une promesse
  • Fourchettes d’amélioration approximatives observées lors de migrations de Go vers Rust :
    • Utilisation CPU : baisse de 20 à 60 %
      • Go étant déjà efficace, l’effet est moins spectaculaire que lors d’un passage de Python à Rust
      • Les gains viennent de l’absence de GC et de boucles plus resserrées
    • Mémoire : baisse de 30 à 50 %
      • Principalement grâce à l’absence de surcoût du GC et à un runtime plus léger
    • Latence P99 : nettement plus régulière
      • Les services Rust ont tendance à présenter une courbe plus lisse, avec moins de jitter provoqué par le GC qu’en Go
      • Go s’est beaucoup amélioré depuis l’introduction de son GC faible latence, mais l’écart subsiste sous forte charge
    • Incidents de production : c’est le domaine d’amélioration que les équipes rapportent le plus volontiers
      • Des bugs comme les data races qui passent go test -race et arrivent quand même en production, les deref de nil ou les chemins d’erreur oubliés ne compilent tout simplement pas en Rust
      • Après une migration vers Rust, les rotations d’astreinte deviennent en général très ennuyeuses
  • Andrew Lamb, Staff Engineer chez InfluxData, raconte dans Rustacean Station: Rebuilding InfluxDB with Rust qu’après la réécriture d’InfluxDB, ils n’avaient plus à traquer des crashes, d’étranges race conditions multithreadées ou d’autres problèmes auparavant très chronophages
  • Passer de Go à Rust a peu de chances d’apporter une amélioration de débit par 10 comme on peut parfois l’observer lors d’un passage de Python à Rust
  • Les vrais bénéfices sont la réduction des « erreurs absurdes », des queues de latence plus plates et la capacité à s’étendre à d’autres domaines, comme l’embarqué ou la programmation système, avec le même langage

Remarques complémentaires

  • Le système de types de Rust n’élimine pas tous les bugs de logique liés à la synchronisation, mais un type qui ne peut pas être partagé entre threads sans synchronisation ne compilera pas
  • Le genre de problème où « on a oublié un verrou » et qui finit en corruption silencieuse des données peut être empêché par le système de types de Rust
  • En Go, string est une séquence immuable d’octets, conventionnellement en UTF-8, mais cela n’est pas garanti au niveau du type
  • La correspondance la plus proche est Go string ↔ Rust &str pour une vue en lecture seule, et Go []byte ↔ Rust Vec<u8> pour un buffer mutable
  • En Rust, String est la version possédée et extensible de &str, avec la garantie supplémentaire que le contenu est un UTF-8 valide
  • Pour plus de détails, voir Strings, bytes, runes and characters in Go
  • Depuis Go 1.18, les fonctions génériques et les types génériques sont possibles, mais les paramètres de type sur les méthodes elles-mêmes n’ont pas été introduits
  • Les chaînes d’itérateurs comme (0..100).filter(|n| ...).collect() peuvent sembler peu familières aux développeurs Go, mais on peut aussi utiliser des boucles for en Rust, et c’est souvent le bon choix pour du code ponctuel

Conclusion

  • Le passage de Go à Rust est différent d’une transition vers Rust depuis Python ou TypeScript
  • Les développeurs venant de Go connaissent déjà les avantages du typage statique et des langages compilés : il ne s’agit donc pas de quitter le typage dynamique ou un runtime lent
  • Le compromis central consiste à abandonner nil pour obtenir une codebase plus robuste, moins de pièges et un compilateur plus strict qui détecte davantage d’erreurs à la compilation
  • En contrepartie, la courbe d’apprentissage est plus raide
  • Pour des services dont l’organisation dépend, qui exigent une forte disponibilité et qui sont critiques pour le business, comme les services fondamentaux, ce compromis vaut clairement le coup
  • Pour d’autres services, Go peut encore rester la bonne réponse
  • Le but d’une migration est d’affecter chaque problème au langage qui le résout le mieux

1 commentaires

 
GN⁺ 3 시간 전
Commentaires sur Hacker News
  • Migrer de C/C++ ou de Python vers Rust se comprend pour plusieurs raisons, mais pour un backend web, Go semble être un choix bien adapté
    J’utilise presque exclusivement Rust, mais la dernière fois que j’ai travaillé sur un serveur web en Rust, je me suis dit que j’aurais dû prendre Go
    L’article original souligne que la syntaxe de gestion des erreurs de Go est verbeuse, et c’est vrai. Rust avait le même problème, puis a ajouté la syntaxe ? qui renvoie la valeur d’erreur en cas d’échec. La gestion des erreurs en Go est le plus souvent une version développée de cela
    Rust n’a pas de type d’erreur unifié, et ses principaux systèmes d’erreurs comme io::Error, thiserror ou anyhow rendent la remontée dans la chaîne d’appels fastidieuse
    Il y a des choses qu’il est difficile d’ajouter plus tard si elles manquent dans un nouveau langage : type des constantes, type booléen, type d’erreur, type de tableau multidimensionnel, types de vecteurs·matrices de taille 2/3/4 et opérations standard, etc. Si rien n’est standardisé tôt, on perd ensuite énormément de temps à harmoniser plusieurs représentations d’un même concept
    En dehors de la gestion des erreurs, c’est moins sensible pour le web, mais en calcul numérique, graphisme ou modélisation, devoir appliquer des opérations standard à des tableaux de nombres devient une vraie souffrance
    Go a deux grands atouts pour les services web. Le premier, c’est ce que l’article appelle les goroutines, et le second, moins traité dans le texte, ce sont les bibliothèques. Go dispose de la plupart des bibliothèques nécessaires aux services web, souvent utilisées aussi en interne chez Google, donc éprouvées dans des conditions très dures. À l’inverse, les crates Rust sont moins mûres et manquent souvent de garantie qualité officielle

    • À mon avis, le plus grand avantage de Go sur Rust, c’est la vitesse de compilation
      Rust dépend aussi encore de nombreuses bibliothèques C/C++ par rapport à Go, ce qui rend souvent plus problématiques la compilation croisée, les builds reproductibles et la génération de binaires statiques
      Le défaut de Go, c’est que son garbage collector est trop simple. En cas de pics de latence, il reste peu de solutions en dehors d’une réécriture douloureuse
    • Rust n’a en réalité pratiquement qu’une seule erreur, à savoir le trait Error
      Les éléments cités ne sont que des façons courantes de l’utiliser, et utiliser simplement Box ne pose aucun problème. C’est en gros similaire à ce que fait anyhow::Error
    • J’ai bien aimé Go pendant un moment, mais depuis que j’utilise davantage Swift et Rust, un compilateur qui n’empêche pas les déréférencements de pointeur nul et ne garantit pas la sûreté en concurrence me paraît un peu préhistorique
      Cela dit, du côté de la bibliothèque standard, je trouve que Go s’en sort bien mieux que Rust
    • D’accord. J’ai surtout remarqué au début que ce texte se présentait comme destiné aux services backend
      J’aime Rust comme langage et je l’utilise pour du firmware embarqué et des applications PC, mais pour le backend web, j’utilise encore Python. Rust n’a pas d’ensemble d’outils du niveau de Django ou Rails
      Il existe des équivalents à Flask, mais pas l’écosystème solide de Flask. J’ai peu d’expérience avec Go, mais pour un backend web, je choisirais probablement Go plutôt que Rust, à cause de l’écosystème de bibliothèques et de frameworks
      Et pour les raisons habituelles, je n’aime pas beaucoup Async Rust. L’écosystème web Rust impose presque partout l’asynchrone
    • Rust n’a pas trois systèmes d’erreurs, mais un seul : le trait Error
      io::Error n’est qu’un des nombreux types qui l’implémentent, sans statut particulier. Les erreurs définies avec thiserror implémentent elles aussi ce trait
      anyhow sert simplement à dire commodément « n’importe quelle Error » lorsqu’on ne veut pas détailler dans le contrat d’API tous les types d’erreurs qu’une fonction peut produire
  • Rust permet plus facilement que Go d’écrire du code déterministe, ce qui est très utile quand on a besoin de tests de simulation déterministes et de tests basés sur les propriétés
    J’ai récemment écrit en Go un outil de mirroring de données Postgres-to-Iceberg, https://github.com/polynya-dev/pg2iceberg, mais je l’ai porté en Rust parce que je voulais faire des tests de simulation déterministes sans me battre contre le runtime Go
    Cela dit, si le domaine concerné n’est pas assez important pour justifier ce niveau de test, je choisirais toujours Go plutôt que Rust
    Article lié : https://www.polarsignals.com/blog/posts/2024/05/28/mostly-ds...

  • Ça va peut-être sembler convenu et répétitif, mais mon principal reproche à Rust concerne la situation de la gestion des paquets, et je pense que c’est entièrement le résultat d’un certain état d’esprit chez les développeurs
    J’aime l’ergonomie côté Rust. L’approche fonctionnelle des types de données est élégante. Mais je travaille en parallèle sur un projet Rust et un projet Go, et l’arbre de dépendances n’a rien à voir
    Le projet Go repose presque entièrement sur la bibliothèque standard, alors que dans le projet Rust, j’ai seulement demandé rusqlite (sqlite), clap (CLI), ratatui (TUI) et tauri (GUI), et j’ai déjà l’impression d’avoir plus de 400 dépendances. tauri en est de loin le principal responsable, mais même sans lui on frôle encore la centaine, ce qui me paraît délirant
    S’il existait de meilleures alternatives de crates Rust, bien maintenues et raisonnables sur les dépendances, ce serait déjà beaucoup mieux, mais je n’en ai pas encore trouvé. Je ne veux simplement pas introduire un shai hulud dans mon système, alors que côté web Rust, on dirait que certains veulent faire de cargo un équivalent de npm

    • Il faut tenir compte du fait que beaucoup de bibliothèques Rust sont découpées en plusieurs crates, qui apparaissent toutes dans le graphe de dépendances
      Cela donne donc l’impression qu’il y a plus de dépendances qu’en réalité. Même séparées en crates distinctes, elles ont souvent le même mainteneur et font partie du même dépôt Git en amont
      Cela dit, je suis d’accord avec l’impression générale. Rust a beaucoup de crates en version 0.x à moitié abandonnées, et il n’existe souvent pas de meilleure alternative
    • Je considère que la bibliothèque standard est l’endroit où les bonnes idées vont mourir
      Et ensuite arrive httplib3, puis httplib4
      Autrement dit, je préfère largement l’approche de Rust. Que je dépende de la bibliothèque standard ou d’une dépendance externe ne change pas grand-chose pour moi : dans tous les cas, cela reste une dépendance
      Le fait que ce soit dans la bibliothèque standard et donc supposément de meilleure qualité ou mieux maintenu relève d’une autre question
      Au final, tout dépend des ressources. Bien sûr, la bibliothèque standard peut en recevoir davantage, mais elle peut aussi devenir obèse et impossible à maintenir
    • Je me demande s’il existe vraiment un langage, en dehors peut-être de Java, qui fournisse dans sa bibliothèque standard l’équivalent de rusqlite, clap, ratatui et tauri
      Il faut aussi voir que Tauri lui-même est composé de 14 crates, chacune apparaissant dans l’arbre de build
      https://github.com/tauri-apps/tauri/blob/dev/Cargo.toml
      Ratatui en compte 6
      https://github.com/ratatui/ratatui/blob/main/Cargo.toml
    • La gestion des paquets est une plaie dans presque tous les langages et toutes les technologies
      Personne ne l’a « résolue », et je doute qu’il existe un jour une solution unique
      Avec Go, il faut faire confiance aux auteurs de bibliothèques pour respecter correctement le versionnage sémantique, et on ne peut pas figer les versions. Personnellement, c’est quelque chose qui m’agace aussi beaucoup
      Il existe quelques contournements : utiliser un SHA comme un hash de commit Git pour fabriquer une pseudo-version, ou recourir au vendoring, qui est un cache de dépendances connu. Mais le vendoring amène aussi ses propres problèmes de gestion de cache
      J’ai dû utiliser un environnement virtuel Python ce week-end, et ça s’est mal terminé ; ça m’a rappelé pourquoi j’avais quitté Python
      Le CPAN de Perl, Maven/Gradle de Java, les gems de Ruby, dep/glide/vgo/modules de Go, Cargo de Rust, npm/yarn de Node, tous ont des problèmes similaires
      Les systèmes d’exploitation aussi : yum/rpm chez Redhat, apt chez Debian, snap chez Ubuntu, etc. Je ne comprends toujours pas particulièrement snap
    • Je ne connais pas bien Go, donc je me demande ce qui, dans sa bibliothèque standard, correspondrait à Tauri
      Dans ce cas d’usage, garder le frontend en Go et ne faire passer que le backend en Rust pourrait peut-être avoir du sens
  • Ce texte paraît étrange parce qu’il essaie à la fois d’être un guide de migration et un plaidoyer pour Rust
    En fin de compte, si l’on hésite entre Rust et Go, la question centrale revient presque entièrement à « veut-on un runtime managé ? » Une génération entière de programmeurs Rust s’est convaincue que les runtimes managés étaient mauvais et que leur absence constituait une fonctionnalité essentielle
    Mais c’est manifestement faux. Il existe plus de domaines de programmation où l’on veut un runtime managé que de domaines où l’on n’en veut pas
    Cela ne signifie pas pour autant que Go doive être le choix par défaut dans tous ces cas. Il existe aussi beaucoup de raisons subjectives de préférer Rust. Quand j’utilise Go, match me manque, mais tokio et Async Rust ne me manquent pas
    Les deux sont des choix légitimes dans presque tous les cas où il n’est pas nécessaire de tordre artificiellement l’espace du problème. Écrire par exemple un module du noyau Linux en Go serait en revanche un choix bizarre
    L’affrontement Rust contre Go ressemble à une marge étrange et embarrassante de notre secteur. Une grande partie de l’industrie construit très bien des systèmes entiers en Python ou en Node, et se moque des originaux qui se disputent pour savoir quel langage compilé à typage statique choisir. La vraie question, c’est Python contre Rust/Go, pas Rust contre Go

    • Utiliser Node avec PureScript pourrait être une option correcte
      Mais de manière générale, les camps Rust et Go devraient unir leurs forces contre les méfaits du typage dynamique. Si les annotations de type sont désormais considérées comme une bonne pratique, n’est-ce pas l’aveu implicite qu’il y avait là un défaut ?
      Même de bonnes annotations de type restent inférieures à l’inférence de types. L’inférence permet de laisser intacte une grande partie du code lors d’un changement de type, tout en empêchant les changements de type involontaires
    • Côté Node, ils ont adopté TypeScript parce qu’ils voulaient des types statiques compilés
      J’aimerais simplement que TS ait un peu plus de runtime. La seule chose que j’envie à Python, c’est la facilité très naturelle avec laquelle on peut faire de la validation de schéma JSON sur des endpoints HTTP
      Le passage obligé par Zod continue de m’irriter, et j’y vois un problème dû au dogmatisme de l’équipe TS
  • Les traces d’écriture LLM deviennent de plus en plus subtiles, mais restent encore assez visibles. Le mot genuine en particulier
    Des phrases comme « This is the area where Go genuinely shines, and it’s worth being precise about why », « the lack of GC pauses is a genuine selling point », « Humans are genuinely bad at reasoning about memory » ou « There are cases where the borrow checker is genuinely too strict » en sont des exemples
    Je ne pense pas que tout le texte soit généré par IA, mais plutôt qu’il a été aidé par IA. Si c’est le cas, l’auteur s’en est genuinely bien sorti
    Comme personne d’autre ne semble le relever, cela n’a sans doute pas nui de manière significative au contenu, mais je trouve étrange que cela devienne si fréquent et si difficile à détecter

    • Je suis d’accord, même si je ne saurais pas vraiment dire pourquoi. Je ne sais pas exactement ce qui donne cette impression d’un texte généré par IA
      C’est vers « Go is clearly working for a lot of people, » que j’ai commencé à soupçonner une aide de l’IA. Bien sûr, ce n’est peut-être pas le cas, et je suis mauvais pour ce genre de détection
      Plus qu’un indice précis, c’est ironiquement une impression. Si un texte « sonne » comme de l’écriture assistée par IA, je perds presque immédiatement mon intérêt, même s’il est correct par ailleurs
      J’aimerais que les gens se sentent plus à l’aise pour écrire directement leurs propres idées comme elles leur viennent
    • Totalement hors sujet, mais it's worth being precise about ... me semble être une tournure bien plus typiquement IA que l’usage de genuine
    • Je pense que tout le texte est généré par IA. L’auteur a peut-être fourni un brouillon comme entrée et retouché une partie de la sortie
      Par exemple, ce paragraphe me donne cette impression : « Go got generics in 1.18, and they’re useful, but the implementation has constraints (no methods with type parameters, GC shape stenciling, occasional surprising performance characteristics). Rust generics monomorphize, each instantiation produces specialized code with zero runtime cost. Combined with traits, this gives you real zero-cost abstractions. »
      Chaque phrase dit quelque chose, chaque phrase est importante et remplit son rôle. C’est le genre d’écriture qu’on attend davantage d’un ouvrage très spécialisé ou d’un article scientifique que d’un billet de blog
      Et cela rend justement la lecture plus difficile et plus ennuyeuse
    • Depuis un an, j’ai l’impression que l’écriture LLM a une tendance particulièrement marquée à parler de la surface et surtout de la couche sous-jacente
      Je ne m’attends pas à ce que les textes générés par LLM soient exempts de formules creuses. J’aimerais simplement que nous montrions tous un meilleur sens de l’édition, afin d’éviter de relire sans cesse la même voix
  • Pour un nouveau projet, rien n’empêche de l’écrire en Rust
    Mais si le code existe déjà, fonctionne et génère des revenus, mieux vaut continuer en corrigeant dans le langage d’origine uniquement les parties qui doivent vraiment être réécrites
    Améliorez le système de façon modeste et mesurable avec un langage que vous connaissez et une équipe en laquelle vous avez confiance. Tout le reste n’est qu’une guerre de religion stérile

    • Si l’équipe a déjà livré avec succès en C#/Java/Go et travaille confortablement avec, je ne vois pas de raison d’utiliser Rust
  • J’aimais déjà Rust avant d’avoir lancé des benchmarks, mais la différence d’efficacité avec laquelle la plupart des LLM écrivent en Rust et en Go était bien plus grande que je ne l’imaginais. C’était encore plus visible avec des harnesses agentiques capables de corriger les problèmes d’environnement initiaux
    En voyant cela, je suis devenu un partisan de Rust assez convaincu. J’ai obtenu de bons résultats en écrivant en Rust des outils de traitement batch appelés depuis une base de code existante, mais je n’ai pas encore tenté une migration complète en production
    Les problèmes de Go évoqués dans l’article, en particulier autour de nil, me semblent de plus en plus gérables avec une revue de code approfondie via Codex. Il vaut évidemment mieux que ces problèmes n’existent pas, mais pour des développeurs qui consacrent autant d’efforts à la revue et à la compréhension qu’à la conception et à l’implémentation, ce type de bug de sécurité devient de plus en plus optionnel
    Les données par langage sont ici : https://gertlabs.com/rankings?mode=agentic_coding

    • Grâce aux messages d’erreur détaillés du compilateur et à son système de types robuste, il est plus facile pour un agent de gérer la boucle corriger → compiler → corriger
      Rust force fortement l’utilisateur à rester sur une trajectoire bien définie. Codex finit toujours par produire quelque chose qui compile
      L’inconvénient, c’est qu’au lieu d’échouer quand une approche idiomatique est parfois impossible, il peut produire une implémentation bête qui compile et satisfait quand même la demande
    • Du point de vue des LLM, le point faible de Rust, c’est le temps de compilation
      Les LLM écrivent du code plus vite que les humains, donc le temps passé à attendre la compilation pèse relativement plus lourd. À partir d’une certaine taille de projet, disons au-delà de 100 000 lignes, une compilation Rust environ 10 fois plus lente commence à devenir un vrai goulet d’étranglement
      Si l’on écrit une infrastructure centrale, ce coût peut valoir la peine, mais pour des services internes non exposés sur Internet, la vitesse de développement peut être plus importante
      Je pense aussi que les compilations lentes affectent la vitesse de développement humaine, mais curieusement les développeurs cherchent très rarement à la quantifier
  • Si la verbosité est le principal obstacle, ceci, prévu pour Go 1.28, devrait la réduire fortement
    https://github.com/golang/go/issues/12854#issue-110104883

  • La formule « service dont l’organisation dépend, nécessitant une forte disponibilité et critique pour l’activité » est amusante
    C’est particulièrement vrai lorsque ce service Rust tourne sur Kubernetes

  • J’utilise déjà Rust et je n’ai pas d’expérience avec Go, donc ce texte n’est peut-être pas vraiment fait pour moi
    Mais un point me gêne : dire qu’en Rust les data races sont « détectées à la compilation » me semble au moins un peu exagéré
    Cette formulation peut laisser croire que Rust gère aussi le starvation sur mutex ou d’autres problèmes de concurrence. En pratique, ce n’est pas le cas
    Je sais que data race est un terme formel au sens étroit, mais je pense quand même qu’on pourrait l’écrire de façon plus claire