Utiliser Rust au démarrage : un récit édifiant
La rouille est géniale, pour certaines choses. Mais réfléchissez-y à deux fois avant de le choisir pour une startup qui a besoin d'aller vite.
J'ai hésité à écrire ce post, car je ne veux pas déclencher ou entrer dans une guerre sainte sur les langages de programmation. (Juste pour se débarrasser de l'appât de la flamme, Visual Basic est le meilleur langage de tous les temps !) Mais un certain nombre de personnes m'ont posé des questions sur mon expérience avec Rust et s'ils devraient choisir Rust pour leurs projets. Donc, j'aimerais partager certains des avantages et des inconvénients que je vois de l'utilisation de Rust dans un environnement de démarrage, où le déplacement rapide et la mise à l'échelle des équipes sont vraiment importants.
Je tiens à préciser que je suis fan de Rust pour certaines choses . Cet article ne traite pas de la façon dont Rust est mauvais en tant que langage ou quoi que ce soit du genre. Ce dont je veux parler, cependant, c'est comment l'utilisation de Rust impliquera presque certainement un coup de productivité non négligeable qui pourrait être un facteur majeur si vous essayez d'aller vite. Évaluez soigneusement si l'impact sur la vélocité vaut les avantages du langage pour votre entreprise et votre produit.
Dès le départ, je dois dire que Rust est très bon dans ce pour quoi il est conçu , et si votre projet a besoin des avantages spécifiques de Rust (un langage système avec des performances élevées, un typage super fort, pas besoin de ramasse-miettes, etc.) alors Rust est un excellent choix. Mais je pense que Rust est souvent utilisé dans des situations où il ne convient pas, et les équipes paient le prix de la complexité et des frais généraux de Rust sans en tirer beaucoup d'avantages.
Ma principale expérience de Rust vient de travailler avec lui pendant un peu plus de 2 ans dans une startup précédente. Ce projet était un produit SaaS basé sur le cloud qui est, plus ou moins, une application CRUD conventionnelle : il s'agit d'un ensemble de microservices qui fournissent un point de terminaison d'API REST et gRPC devant une base de données, ainsi que d'autres back- fin des microservices (eux-mêmes implémentés dans une combinaison de Rust et Python). Rust a été utilisé principalement parce que quelques-uns des fondateurs de la société étaient des experts de Rust. Au fil du temps, nous avons considérablement élargi l'équipe (augmentant les effectifs d'ingénierie de près de 10 fois), et la taille et la complexité de la base de code ont également considérablement augmenté.
Au fur et à mesure que l'équipe et la base de code grandissaient, j'ai senti qu'au fil du temps, nous payions une taxe de plus en plus lourde pour continuer à utiliser Rust. Le développement était parfois lent, le lancement de nouvelles fonctionnalités prenait plus de temps que prévu, et l'équipe ressentait un réel coup de productivité depuis cette première décision d'utiliser Rust. Réécrire le code dans une autre langue aurait, à long terme, rendu le développement beaucoup plus agile et accéléré le délai de livraison, mais trouver le temps pour le travail de réécriture majeur aurait été extrêmement difficile. Nous étions donc un peu coincés avec Rust à moins que nous ne décidions de mordre la balle et de réécrire une grande partie du code.
La rouille est censée être la meilleure chose depuis le pain en tranches, alors pourquoi cela ne fonctionnait-il pas si bien pour nous ?
Rust a une énorme courbe d'apprentissage.
J'ai travaillé dans des dizaines de langages au cours de ma carrière, et à quelques exceptions près, la plupart des langages procéduraux modernes (C++, Go, Python, Java, etc.) tous très similaires en termes de concepts de base. Chaque langue a ses différences, mais il s'agit généralement d'apprendre quelques modèles clés qui diffèrent d'une langue à l'autre, puis on peut être productif assez rapidement. Avec Rust, cependant, il faut apprendre des idées entièrement nouvelles – des choses comme les durées de vie, la propriété et le vérificateur d'emprunt. Ce ne sont pas des concepts familiers à la plupart des personnes travaillant dans d'autres langages courants, et la courbe d'apprentissage est assez abrupte, même pour les programmeurs expérimentés.
Certaines de ces "nouvelles" idées sont, bien sûr, présentes dans d'autres langages - en particulier les langages fonctionnels - mais Rust les introduit dans un cadre de langage "traditionnel", et sera donc nouveau pour de nombreux nouveaux arrivants de Rust.
Bien qu'ils soient parmi les développeurs les plus intelligents et les plus expérimentés avec lesquels j'ai travaillé, de nombreux membres de l'équipe (moi y compris) ont eu du mal à comprendre les moyens canoniques de faire certaines choses dans Rust, comment traiter les messages d'erreur souvent obscurs du compilateur, ou comment comprendre le fonctionnement des bibliothèques clés (plus d'informations à ce sujet ci-dessous). Nous avons commencé à organiser des sessions hebdomadaires "apprendre Rust" pour l'équipe afin de partager les connaissances et l'expertise. Tout cela a considérablement pesé sur la productivité et le moral de l'équipe, car tout le monde ressentait la lenteur du développement.
À titre de comparaison de ce à quoi ressemble l'adoption d'un nouveau langage dans une équipe logicielle, l'une de mes équipes chez Google a été l'une des premières à passer entièrement de C++ à Go, et il n'a pas fallu plus d'environ deux semaines avant que l'ensemble Une équipe de 15 personnes codait assez confortablement en Go pour la première fois. Avec Rust, même après des mois de travail quotidien dans la langue, la plupart des membres de l'équipe ne se sont jamais sentis pleinement compétents. Un certain nombre de développeurs m'ont dit qu'ils étaient souvent gênés que cela prenne plus de temps que prévu pour que leurs fonctionnalités atterrissent et qu'ils passaient si longtemps à essayer de comprendre Rust.
Il existe d'autres moyens de résoudre les problèmes que Rust tente de résoudre.
Comme mentionné ci-dessus, le service que nous construisions était une application CRUD assez simple. La charge attendue sur ce service allait être de l'ordre de quelques requêtes par seconde, maximum, pendant la durée de vie de ce système particulier. Le service était une interface vers un pipeline de traitement de données assez élaboré qui pouvait prendre plusieurs heures à s'exécuter, de sorte que le service lui-même ne devait pas être un goulot d'étranglement des performances. Il n'y avait aucune crainte particulière qu'un langage conventionnel comme Python ait du mal à fournir de bonnes performances. Il n'y avait pas de besoins particuliers en matière de sécurité ou de simultanéité au-delà de ce que tout service Web doit gérer. La seule raison pour laquelle nous utilisions Rust était que les auteurs originaux du système étaient des experts de Rust, et non parce qu'il convenait particulièrement bien à la création de ce type de service.
Rust a décidé que la sécurité est plus importante que la productivité des développeurs. C'est le bon compromis à faire dans de nombreuses situations - comme la construction de code dans un noyau de système d'exploitation ou pour les systèmes embarqués à mémoire limitée - mais je ne pense pas que ce soit le bon compromis dans tous les cas, surtout pas dans les startups où la vitesse est cruciale. Je suis pragmatique. Je préférerais de loin que mon équipe consacre du temps au débogage des fuites de mémoire occasionnelles ou des erreurs de type pour le code écrit, par exemple, en Python ou Go, plutôt que de voir tous les membres de l'équipe subir un coup de productivité 4x pour avoir utilisé un langage conçu pour éviter ces problèmes entièrement .
Comme je l'ai mentionné ci-dessus, mon équipe chez Google a créé un service, entièrement en Go, qui au fil du temps a grandi pour prendre en charge plus de 800 millions d'utilisateurs et quelque chose comme 4x le QPS de Google Search à son apogée. Je peux compter d'une part le nombre de fois où nous avons rencontré un problème causé par le système de type Go ou le ramasse-miettes au cours des années de construction et d'exploitation de ce service. Fondamentalement, les problèmes que Rust est conçu pour éviter peuvent être résolus d'autres manières - par de bons tests, un bon linting, une bonne révision du code et une bonne surveillance. Bien sûr, tous les projets logiciels n'ont pas ce luxe, donc je peux imaginer que Rust peut être un bon choix dans ces autres situations.
Vous aurez du mal à embaucher des développeurs Rust.
Nous avons embauché une tonne de personnes pendant mon séjour dans cette entreprise, mais seulement deux ou trois des plus de 60 personnes qui ont rejoint l'équipe d'ingénierie avaient une expérience antérieure avec Rust. Ce n'était pas faute d'essayer de trouver des développeurs Rust - ils ne sont tout simplement pas là. (De même, nous hésitions à embaucher des personnes qui ne voulaient coder qu'en Rust, car je pense que c'est une mauvaise attente à mettre en place dans un environnement de démarrage où les choix de langage et d'autres technologies doivent être faits de manière agile.) Cette rareté Les talents de développement de Rust changeront avec le temps, à mesure que Rust deviendra plus courant, mais construire autour de Rust en partant du principe que vous pourrez embaucher des personnes qui le savent déjà semble risqué.
Un autre facteur secondaire est que l'utilisation de Rust conduira presque certainement à un schisme entre les membres de l'équipe qui connaissent Rust et ceux qui ne le connaissent pas. Parce que nous avions choisi un langage de programmation "ésotérique" pour ce service, les autres ingénieurs de l'entreprise qui auraient autrement pu être utiles pour créer des fonctionnalités, déboguer des problèmes de production, etc. queues de la base de code Rust. Ce manque de fongibilité au sein de l'équipe d'ingénierie peut être un véritable handicap lorsque vous essayez d'agir rapidement et d'exploiter les forces combinées de tous les membres de l'équipe. D'après mon expérience, les gens ont généralement peu de difficulté à se déplacer entre des langages comme C++ et Python, mais Rust est suffisamment nouveau et suffisamment complexe pour constituer un obstacle pour les personnes travaillant ensemble.
Les bibliothèques et la documentation sont immatures.
C'est un problème qui (j'espère !) sera résolu avec le temps, mais comparé à, disons, Go, la bibliothèque et l'écosystème de documentation de Rust sont incroyablement immatures. Maintenant, Go avait l'avantage d'être développé et pris en charge par toute une équipe dédiée chez Google avant sa sortie dans le monde, de sorte que les documents et les bibliothèques étaient assez polis. La rouille, en comparaison, a longtemps été ressentie comme un travail en cours. La documentation de nombreuses bibliothèques populaires est assez rare et il faut souvent lire le code source d'une bibliothèque donnée pour comprendre comment l'utiliser. C'est mauvais.
Les apologistes de Rust de l'équipe disaient souvent des choses comme "async/wait sont encore vraiment nouveaux" et "ouais les docs pour cette bibliothèque manquent", mais ces lacunes ont eu un impact assez important sur l'équipe. Nous avons commis une énorme erreur dès le début en adoptant Actix comme cadre Web pour notre service, une décision qui a entraîné d'énormes souffrances alors que nous rencontrions des bogues et des problèmes profondément enfouis dans la bibliothèque que personne ne savait comment résoudre. (Pour être juste, c'était il y a quelques années et peut-être que les choses se sont améliorées maintenant.)
Bien sûr, ce genre d'immaturité n'est pas vraiment spécifique à Rust, mais cela équivaut à une taxe que votre équipe doit payer. Peu importe la qualité de la documentation et des didacticiels du langage de base, si vous ne savez pas comment utiliser les bibliothèques, cela n'a pas beaucoup d'importance (à moins que vous ne prévoyiez de tout écrire à partir de zéro, bien sûr).
Rust rend l'ébauche de nouvelles fonctionnalités très difficile.
Je ne connais personne d'autre, mais au moins pour moi, lorsque je crée une nouvelle fonctionnalité, je n'ai généralement pas tous les types de données, les API et d'autres détails précis élaborés à l'avance. Je suis souvent en train de péter du code en essayant de faire fonctionner une idée de base et de vérifier si mes hypothèses sur la façon dont les choses devraient fonctionner sont plus ou moins correctes. Faire cela dans, disons, Python est extrêmement facile, car vous pouvez jouer rapidement et librement avec des choses comme la frappe et ne pas vous inquiéter si certains chemins de code sont cassés pendant que vous ébauchez votre idée. Vous pouvez revenir en arrière plus tard et tout ranger, corriger toutes les erreurs de type et écrire tous les tests.
Dans Rust, ce type de "projet de codage" est très difficile, car le compilateur peut se plaindre et se plaindra de tout ce qui ne passe pas la vérification du type et de la durée de vie - comme il est explicitement conçu pour le faire. Cela est parfaitement logique lorsque vous devez créer votre implémentation finale prête pour la production, mais c'est absolument nul lorsque vous essayez de créer quelque chose ensemble pour tester une idée ou mettre en place une base de base. La unimplemented!
macro est utile jusqu'à un certain point, mais nécessite toujours que tout soit vérifié de haut en bas dans la pile avant même que vous puissiez compiler.
Ce qui est vraiment mordant, c'est lorsque vous avez besoin de changer la signature de type d'une interface porteuse et que vous passez des heures à changer chaque endroit où le type est utilisé uniquement pour voir si votre coup initial sur quelque chose est faisable. Et puis refaire tout ce travail quand vous réalisez que vous devez le changer à nouveau.
En quoi Rust est-il bon ?
Il y a certainement des choses que j'aime dans Rust et des fonctionnalités de Rust que j'aimerais avoir dans d'autres langues. La match
syntaxe est excellente. Les traits , et sont vraiment puissants, et l' Option
opérateur Result
est un moyen élégant de gérer les erreurs. Beaucoup de ces idées ont des équivalents dans d'autres langages, mais l'approche de Rust avec elles est particulièrement élégante.Error
?
J'utiliserais absolument Rust pour des projets nécessitant un haut niveau de performance et de sécurité et pour lesquels je n'étais pas terriblement inquiet de la nécessité de faire évoluer rapidement des parties importantes du code avec toute une équipe qui grandit rapidement. Pour des projets individuels ou de très petites équipes (par exemple, 2 à 3 personnes), Rust serait probablement très bien. Rust est un excellent choix pour des éléments tels que les modules du noyau, les micrologiciels, les moteurs de jeu, etc., où les performances et la sécurité sont primordiales, et dans les situations où il peut être difficile de faire des tests vraiment approfondis avant l'expédition.
D'accord, maintenant que j'ai suffisamment énervé la moitié des lecteurs de Hacker News, je suppose que c'est le moment idéal pour annoncer le sujet de mon prochain article : Pourquoi nano
l'éditeur de texte est-il supérieur ? À la prochaine!