3 points par GN⁺ 2023-10-26 | 1 commentaires | Partager sur WhatsApp
  • Après être passé de JavaScript à Rust pour se concentrer sur WebAssembly, l’auteur a évalué la valeur réelle de Rust sur 3 ans en créant Wick, des déploiements en production, un ebook et environ 100 paquets sur crates.io
  • Le borrow checker, le système de types riche, les patterns fonctionnels et l’absence de null empêchent de nombreuses erreurs à la compilation et permettent de maintenir une grande base de code avec peu de tests
  • Clippy et les Cargo workspaces sont puissants, mais les lacunes des outils et de l’écosystème, comme la configuration globale des lint ou la publication de workspaces, se traduisent par un coût opérationnel
  • L’async, le refactoring et la gestion des generic, lifetime et trait constraints restent des zones de friction plus importantes qu’en JavaScript ou en Go
  • Rust est solide et polyvalent, mais les coûts de recrutement, d’apprentissage, d’itération rapide et d’investigation des problèmes sont élevés, ce qui le rend plus adapté quand le périmètre est clair ou que le coût initial peut être absorbé

WebAssembly a conduit au choix de Rust

  • Il y a quelques années, l’auteur a mis de côté ses travaux existants pour se consacrer à 100 % à WebAssembly, et à l’époque Rust offrait le meilleur support de compilation vers WebAssembly
  • Les runtimes WebAssembly riches en fonctionnalités étaient eux aussi basés sur Rust, ce qui en faisait l’option la plus réaliste parmi les choix possibles
  • Il a ensuite créé Wick, un framework d’applications et un runtime qui utilisent WebAssembly comme système de modules central
  • En 3 ans, avec plusieurs déploiements en production, un ebook et environ 100 paquets publiés sur crates.io, son expérience de Rust s’est approfondie

Maintenir plus de code avec moins de tests

  • En Rust, l’auteur écrivait d’abord des tests comme dans n’importe quel langage, avant de réaliser qu’il produisait des tests qui ne pouvaient pas échouer dès lors que le code compilait
  • En évitant les blocs unsafe {} et les méthodes sujettes aux panic comme .unwrap(), de nombreux problèmes sont évités par défaut
  • Le borrow checker, le système de types riche, les patterns et bibliothèques fonctionnels, ainsi que l’absence de valeur null, réduisent l’effort consacré aux tests
  • Le projet Wick, avec plus de 70 000 lignes de code, a pu être maintenu avec bien moins de tests que ce qui aurait été nécessaire dans d’autres langages
  • Quand des tests sont nécessaires, le harness de tests d’intégration de Rust permet de les ajouter facilement au plus près du code

Rust change aussi les habitudes de code dans les autres langages

  • Le compilateur Rust signale sans cesse des problèmes dans du code qui paraissait normal dans d’autres langages, et ce processus a fini par transformer les habitudes de développement de l’auteur
  • Désormais, dans d’autres langages aussi, un ordre de lignes de code étrange ou un retour de fonction non vérifié lui paraît inconfortable
  • Même face à des erreurs d’exécution, il ressent désormais une réticence bien plus forte qu’avant
  • La rigueur de Rust est contraignante, mais une fois habitué à la protection offerte par le compilateur, il devient difficile de revenir à d’autres langages

Clippy est utile bien au-delà d’un simple linter

  • Clippy est le linter de Rust, mais il ressemble davantage à un assistant bienveillant qui propose du code alternatif qu’à un simple outil de vérification
  • La bibliothèque standard de Rust est très vaste, et ses nombreuses fonctionnalités sont dispersées entre types, traits, macros et fonctions, ce qui rend l’API recherchée difficile à trouver
  • Plusieurs règles détectent des patterns courants qui peuvent être mieux remplacés par des méthodes ou types de la bibliothèque standard
  • Les centaines de règles couvrent la performance, la lisibilité et les indirections inutiles, et proposent si possible du code de remplacement
  • Une configuration globale des lint à l’échelle du projet semblait devoir devenir possible via une issue Cargo, mais en attendant Wick devait mettre à jour automatiquement par script les configurations de lint inline de dizaines de crates

L’écosystème comporte des lacunes qu’il faut accepter

  • Le problème de configuration globale de Clippy n’est qu’un exemple parmi d’autres des lacunes de l’écosystème souvent rencontrées dans les outils et bibliothèques Rust
  • Les issues concernées sont désormais fermées, mais elles sont restées ouvertes pendant des années et leur résolution a pris du temps
  • Rust attire de nouveaux utilisateurs au point d’avoir longtemps été élu « langage le plus apprécié », mais cet élan ne s’est pas traduit immédiatement par une amélioration spectaculaire des bibliothèques et des outils
  • Il arrivait souvent que des forks ponctuels apparaissent pour gérer un cas d’usage précis, et Wick a connu une situation similaire en essayant de soumettre des PR
  • Parmi les raisons possibles figurent la pression pour maintenir des API stables et la granularité du système de types
    • Les mainteneurs de bibliothèques ont du mal à accepter de petits changements, car ils peuvent entraîner une montée de version majeure
    • Écrire du code Rust capable de satisfaire les besoins de tout le monde représente aussi une charge importante

Cargo, crates.io et les frictions autour de la publication depuis un workspace

  • La structure du dépôt de Wick a été conçue en s’inspirant de projets populaires et paraissait raisonnable au départ, mais des problèmes sont apparus à l’étape de publication
  • Avec Cargo, il est facile de construire, tester et utiliser des crates de taille modulaire, mais la publication sur crates.io est un problème distinct
  • Sur crates.io, tous les crates référencés doivent être publiés individuellement pour qu’un paquet puisse être publié
    • Il est logique d’empêcher la publication d’un crate qui dépend d’un paquet présent uniquement sur le système de fichiers local
    • Mais dans une structure naturelle où un grand projet est découpé en petits modules internes, il n’est pas possible de publier un crate en incluant des sous-crates qui n’existent qu’à l’intérieur du crate parent
  • Une correction a été apportée : un crate avec une dev dependency locale peut être publié sans inclure de version dans le Cargo.toml
  • Le support des Cargo workspaces lui-même est excellent, et l’expérience de gestion de grands projets est meilleure que dans la plupart des autres langages
  • Cependant, les workspaces ne résolvent pas le problème de publication et, malgré les différentes approches de configuration, il reste difficile de trouver une « bonne réponse » qui se publie facilement
  • L’existence même de nombreux crates utilitaires autour de cargo workspace publish montre bien le problème
  • Lors de la publication de Wick, il fallait souvent plus d’une heure en combinant des tâches manuelles répétitives et des outils ne fonctionnant que partiellement

L’async est l’une des plus grandes sources de friction

  • L’async en Rust donne l’impression d’avoir été ajouté après la création initiale du langage, et dans la pratique il gêne souvent comme une fonctionnalité greffée tardivement
  • Les erreurs sont difficiles à comprendre et à résoudre, et même lorsqu’on cherche une solution il faut trier selon plusieurs runtimes et leurs propres approches de l’async
  • Certaines bibliothèques async peuvent ne pas être utilisables en dehors d’un runtime async spécifique
  • Pour quelqu’un qui a 20 ans de JavaScript et aussi de l’expérience en Go, l’async de Rust est la plus grande source de frustration et de friction
  • Ce n’est pas un problème insurmontable, mais il faut toujours être prêt à voir surgir des problèmes d’async à tout moment
  • Dans d’autres langages, l’async fonctionne presque de manière invisible, de façon naturelle

Le refactoring peut devenir une tâche éprouvante

  • Le système de types riche de Rust est à la fois une force et une faiblesse
  • Penser avec les types Rust est agréable, mais gérer les types Rust peut tourner au cauchemar
  • Les données et les signatures de fonction peuvent inclure des generic type, generic lifetime et trait constraints
  • Les constraints elles-mêmes peuvent à nouveau avoir des generic type et des lifetime, au point qu’il y a parfois plus de constraints de type que de code réel
  • Comme il faut définir les generics pour chaque impl, c’est déjà fastidieux à l’écriture, et en refactoring un petit changement peut déclencher une cascade de modifications
  • Lorsqu’il faut répéter les mêmes constraints ou listes de generics à plusieurs endroits, il n’existe pas au niveau du langage ou des outils de moyen pour les aliaser ou les référencer depuis une définition centrale, ce qui laisse subsister une charge de duplication

Jugement final : puissant, mais coûteux

  • Rust est assez polyvalent pour écrire avec le même langage du code système, des applications CLI, des serveurs web et des clients web
  • Avec WebAssembly, il est possible d’exécuter le même binaire de LLM dans le navigateur et en ligne de commande
  • Les programmes Rust peuvent être extrêmement robustes, et une fois qu’on a fait l’expérience des problèmes que Rust empêche, il devient difficile de revenir à d’autres langages
  • Quand l’auteur est brièvement revenu à Go, la vitesse de développement lui a de nouveau semblé séduisante, mais après avoir subi un panic à l’exécution, cet avantage s’est atténué
  • Rust a des inconvénients bien réels
    • Le recrutement est difficile
    • L’apprentissage est lent
    • Le langage est trop rigide pour l’itération rapide
    • Il est difficile de traquer les problèmes de mémoire et de performance, surtout dans le code async
    • Toutes les bibliothèques ne sont pas assez bonnes pour du code sûr
    • Les outils de développement ont encore une grande marge d’amélioration
  • L’auteur a réalisé des choses impressionnantes avec une petite équipe, mais a aussi rencontré de gros obstacles, et comme il existait des raisons techniques solides de choisir Rust pour Wick, il estime qu’il est encore trop tôt pour dire si Rust en valait la peine
  • Si l’on doit itérer rapidement, Rust a de fortes chances de ne pas être le bon choix
  • Si le périmètre est connu ou que l’on peut absorber un coût initial plus élevé, Rust mérite d’être sérieusement envisagé
  • À mesure que la perspective WebAssembly gagne en importance chaque mois, la possibilité de réutiliser partout un logiciel robuste écrit une seule fois devient de plus en plus réaliste

1 commentaires

 
GN⁺ 2023-10-26
Avis sur Hacker News
  • J’ai beaucoup utilisé Rust, mais même après des années, il me semble toujours peu productif
    Ces temps-ci, j’utilise beaucoup Zig, et j’ai l’impression d’être environ 10 fois plus productif, parce que je peux me concentrer uniquement sur le code que je veux écrire sans me demander quels outils ou bibliothèques utiliser
    Je sais que Rust apporte la sécurité mémoire et que c’est important, mais son ergonomie est vraiment mauvaise. Chaque fois que j’utilise Rust, j’ai l’impression d’être contraint, et comme je dois constamment chercher des bibliothèques ou la bonne manière de faire les choses, je ne peux pas simplement « taper du code »
    Le système de types peut lui aussi devenir incontrôlable, et il est souvent difficile de savoir quelles méthodes peuvent réellement être appelées sur telle ou telle structure. Rust est un excellent outil et résout beaucoup de problèmes, mais je ne le considère pas comme un bon langage généraliste

    • Ce ressenti n’est pas universel. Dès qu’une tâche est un peu plus complexe qu’un script shell, j’utilise Rust, et je contrôle même mon gestionnaire de fenêtres avec un programme Rust
      Même en ayant utilisé Python pendant près de 20 ans, je travaille maintenant aussi vite en Rust qu’en Python
    • D’accord. À l’heure actuelle, je me sens bien plus productif en C et C++ qu’en Rust
      À mon sens, Rust a complètement raté le bon compromis. Il est trop pointilleux sur les détails bas niveau pour écrire des applications haut niveau, et trop complexe pour l’embarqué ou les systèmes d’exploitation
      Pour le premier cas, je choisirais C++, Java, Haskell, OCaml, voire Go avec un peu de C ; pour le second, le C utilisé comme une sorte d’assembleur à macros convient bien mieux
      J’ai toujours le sentiment que la vision originale de Graydon Hoare — quelque chose de plus proche de OCaml/SML avec types linéaires, garbage collection, allocation sur la pile, green threads et CPS — aurait donné un langage bien meilleur
    • Mon expérience est exactement l’inverse. En utilisant Rust dans des systèmes embarqués, j’ai gagné énormément en confiance et en rapidité
      En C, une petite erreur conduit souvent à un comportement indéfini et à des ennuis ; avec Rust, ça n’existe pas, et ça change complètement la donne
    • Je serais curieux de savoir quel type de code tu écris. Très bas niveau, ou au contraire très haut niveau ?
      Je me demande si tu veux dire qu’avec Zig, il n’est pas nécessaire de chercher des bibliothèques ni de comprendre comment faire
    • Je suis d’accord avec l’impression générale, mais c’est difficile à formuler
      Rust oblige à décider à l’avance où vont chaque bit et chaque octet, dans quel thread ils seront utilisés et selon quel mode de mutation ils seront manipulés. Si l’on n’est pas au niveau d’un parseur ou d’un microcontrôleur, ce processus paraît fastidieux
      J’aime d’abord faire fonctionner quelque chose, puis déterminer la meilleure structure d’API ; Rust entre en conflit avec cette façon de faire
      Même si le système de types de Rust est plus puissant, Swift permet d’obtenir 90 % des performances avec un flux beaucoup plus naturel
  • Le fait que crates.io n’ait pas d’espaces de noms est peut-être la critique la plus importante
    N’importe qui peut réserver un nom de paquet générique dans l’espace global, et sauf à éviter le dépôt crates.io, la plupart des gens doivent s’en accommoder. Or certains paquets portant ces noms génériques réservés ne sont pas, en réalité, les meilleurs à utiliser
    C’était peut-être une réaction à la notation DNS inversée à la Java, verbeuse et pénible, mais une approche à la GitHub, avec des espaces de noms utilisateur/groupe préfixant le nom du paquet, aurait été un bon compromis

    • J’ai fait une analyse pour trouver les squatteurs de noms sur crates.io, et j’ai calculé que le principal squatteur créait des crates à un rythme d’environ une toutes les 30 secondes, toute la semaine
      J’ai envoyé cette analyse à l’équipe crates.io, en signalant aussi l’existence d’une politique interdisant l’automatisation, mais ils ont répondu que ce n’était pas une preuve suffisante de réservation abusive de noms
      Le problème de crates.io, c’est que même quand une politique claire existe, elle n’est pas appliquée. Résultat : tous les noms de crates courts et faciles à retenir sont déjà pris, et il n’y a aucun moyen de les récupérer
    • Plutôt qu’une réaction à la notation DNS inversée de Java, cela ressemble davantage au fait d’avoir suivi les conventions établies par la plupart des gestionnaires de paquets de l’époque
      NPM, PyPI, RubyGems, Hex d’Elixir, Cabal de Haskell, etc. : autour de 2014-2015, lorsque Rust est apparu, il ne me vient pas vraiment d’exemple de gestionnaire de paquets non Java qui n’ait pas eu un espace de noms global unique
      Certains ont ensuite essayé de corriger cela, mais à l’époque, c’était simplement ainsi que fonctionnaient les gestionnaires de paquets
    • Maven et Java ne sont pas assez reconnus pour le fait que leur gestion des dépendances fonctionne bien
      Les systèmes de gestion des dépendances, moins bons, apparus ensuite dans d’autres langages ont très peu appris des exemples précédents
    • Utiliser des URL pour les paquets est une approche assez logique. Elle fonctionne bien dans l’écosystème Go
      Elle a aussi l’avantage de supprimer le besoin d’une base de données globale de paquets au niveau du langage. Si vous publiez un paquet sur example.com/your-thing, il est en quelque sorte immédiatement publié
      Bien sûr, on peut fournir séparément des caches et des moteurs de recherche si on le souhaite
    • Je n’utilise pas souvent Rust, mais ce genre de problème est vraiment agaçant dans les dépôts de paquets en général
      C’est du genre : http-server est mauvais, il ne faut pas l’utiliser, il faut utiliser MuffinTop — et il faut juste le savoir
      L’idée de noms de paquets officiels est intéressante, mais si le code derrière un alias change avec le temps, cela risque en pratique de devenir déroutant
      Au final, cela restera probablement une partie du processus qui consiste à devenir expert d’un domaine dans n’importe quel écosystème
  • Si l’on crée un .cargo/config.toml à la racine du workspace, il s’applique à toutes les crates, ce qui permet de configurer des lints Clippy globaux
    Dans le fichier, il suffit de mettre sous [build] quelque chose comme rustflags = ["-Wclippy::lint_name_to_warn", "-Dclippy::lint_name_to_deny"]
    Attention toutefois : rustflags remplace les valeurs au lieu de s’y ajouter, donc s’il existe une autre source comme la variable d’environnement RUSTFLAGS, elle écrasera ce réglage

    • Nous exécutons au moment du commit un script qui ajoute les lints aux fichiers lib.rs ou main.rs. C’est simple
  • J’apprends Rust parce qu’il semble évident qu’il va devenir important professionnellement. J’aimerais vraiment l’aimer et j’en vois les avantages, mais il fait partie des langages les plus désagréables que j’aie utilisés jusqu’ici
    J’ai continué à espérer qu’en gagnant en compétence, mon aversion disparaîtrait, mais plus je monte la courbe d’apprentissage, moins je m’y attache vraiment
    Ce n’est pas grave. Ce ne sera sans doute pas le seul langage que je maîtrise tout en le détestant. Mais tant de gens disent aimer Rust que je pensais que moi aussi j’y prendrais plaisir

    • À titre de contre-exemple, moi, j’aime programmer en Rust. La période où je me battais avec le borrow checker est terminée depuis longtemps, et ces temps-ci les erreurs sont rares, sauf quand je les provoque exprès pour vérifier des types
      Le compilateur Rust semble aussi s’être amélioré pour accepter un éventail plus large de cas valides
      La clé pour comprendre le borrow checker a été de comprendre le modèle mémoire sous-jacent. Le modèle mémoire de Rust est le même que celui de C, à l’exception des extensions destinées aux abstractions comme les génériques
      Les règles du borrow checker semblent arbitraires au début, mais elles sont profondément liées à ce modèle mémoire. Le vrai intérêt, c’est quand on se fait surprendre par le borrow checker sans l’avoir voulu, car cela révèle un bug dû à une baisse d’attention
      Ce qui fait peur, c’est que dans des langages comme C ou C++, un tel code pourrait simplement être accepté et continuer son chemin
      Le système de types strict et le borrow checker de Rust poussent en douceur à structurer correctement le code, et je suis convaincu qu’ils ont amélioré ma conception du code dans tous les langages que j’utilise
    • J’ai essayé plusieurs fois d’apprendre Rust, mais je décroche à chaque fois. J’ai juste l’impression que l’utilisabilité est mauvaise
    • Ce qui me bloque encore, c’est l’absence d’arguments de fonction par défaut/nommés
      C’est une fonctionnalité d’utilisabilité très basique pour les programmeurs, partagée par la plupart des langages populaires, et même C++ a des arguments par défaut depuis longtemps
    • Je suis curieux de savoir ce que tu n’aimes pas
  • Ancien programmeur C/C++ qui appelait pthread presque chaque semaine pendant 10 ans, j’utilise maintenant Rust asynchrone partout
    Je ne comprends pas pourquoi l’asynchrone est autant détesté. À mon avis, tout le monde devrait utiliser l’asynchrone pour tout. Même pour les tâches « simples » qui, en apparence, sont monothread

    • Je pense que le problème est son caractère contagieux. Surtout dans les environnements embarqués ou Wasm, l’asynchrone dominant n’est pas forcément celui que je veux
      Si le cas d’usage de l’auteur avait été Wasm, il aurait certainement eu un autre point de vue
      Les travaux qui utilisent de gros buffers ou les réutilisent pour éviter le coût des allocations bénéficient souvent encore des anciens pools de threads. On peut en partie régler cela avec des allocateurs bump ou shardés, mais pour des boucles serrées vectorisables et limitées par le CPU, les pools de threads fonctionnent mieux
      L’asynchrone est un bon outil, mais il n’y a pas que des contextes où il est optimal
    • J’ai récemment un peu utilisé Rust asynchrone pour comparer la gestion des erreurs dans des appels asynchrones imbriqués avec Go, et même le petit exemple trivial que je voulais écrire semblait impossible sans Tokio
      Go, C# et TypeScript n’ont pas ce genre de barrière
      Par exemple, il semble qu’on ne puisse même pas utiliser await dans la fonction main sans le décorateur importé de Tokio
    • Le problème auquel je me heurte actuellement, c’est que certains éléments comme rhai Engine ne sont pas Send, alors que j’essaie de les utiliser dans une closure asynchrone
      GPT-4 m’a suggéré de créer un tokio Runtime dans un thread et d’utiliser block_on(). Je vais essayer demain. C’est mon premier vrai projet Rust
    • Moi, j’ai appris l’inverse. Les programmeurs ont tendance à tout rendre inutilement asynchrone, même quand ce n’est pas nécessaire, ce qui augmente la complexité et la charge mentale
    • Peux-tu détailler un peu ? Je me demande quel serait un exemple de tâche simple, apparemment monothread, pour laquelle on choisirait l’asynchrone
      Je me demande aussi si « maintenant j’utilise Rust asynchrone partout » veut dire « j’utilise Tokio partout »
  • Programmer en Rust n’a vraiment rien d’une relation abusive. Le compilateur essaie d’aider autant que possible, et les messages d’erreur de rustc, en particulier, sont parmi les meilleurs au monde

    • Le système d’exploitation attend du programmeur qu’il gère correctement les ressources, et le compilateur Rust rend cela très facile
      Ce n’est pas le compilateur Rust qui ressemble le plus à une relation abusive, mais plutôt le système d’exploitation. Le matériel aussi, puisqu’il faut exécuter correctement l’assembleur ; vu comme ça, on pourrait aussi parler de relation abusive
      Les messages d’erreur de Rust sont incomparables, et aucun compilateur ne s’en approche
      Au passage, j’ai utilisé LaTeX récemment et les messages d’erreur étaient atroces. Essayer de comprendre ce qui n’allait pas en examinant les erreurs était un cauchemar
    • Ils sont globalement bons, mais je déteste vraiment le fait que n’importe quelle erreur dans une fonction asynchrone produise, à chaque point d’appel récursif, une erreur indiquant que le Future n’est plus Send ni Sync
      Toute la console se retrouve couverte d’erreurs, et la vraie erreur de syntaxe est enfouie quelque part au milieu
    • Le compilateur Rust a été le premier compilateur que j’aie vu utiliser le mot « perhaps »
      Mon principal reproche est que je ne comprends toujours pas complètement les durées de vie (lifetimes), et que le compilateur ne peut pas m’aider à chaque fois. Je comprends pourquoi, car le compilateur raisonne de manière conservatrice
  • Dans les tests C++, on dit souvent : « si ça compile, c’est probablement correct »
    Si l’on pense que Rust gère tellement d’erreurs que les cas de test courants deviennent inutiles, j’y vois le signe qu’on ne testait pas ce qu’il fallait dans les autres langages
    Ce qu’il faut tester, ce ne sont pas les problèmes du langage lui-même, mais la logique métier
    Si, en regardant un test, on se dit « je le testerais en JavaScript, mais pas besoin en Rust », alors on peut tout simplement supprimer ce test

    • J’ai déjà entendu « si ça compile, c’est probablement correct » à propos de Haskell et de Rust, mais jamais appliqué au C++
    • Une exception de pointeur nul est un bug qui casse la logique métier
      La distinction « logique métier contre problème de langage » n’existe pas. Le langage est le socle sur lequel repose la logique métier
      Si l’on ne teste pas les modes d’échec, je ne vois pas bien à quoi servent les tests
    • Il y a quelque chose d’étrangement impressionnant chez le type de programmeur C++ qui voit du code poubelle cassé compiler et se dit que c’est sans doute correct
      Contrairement à la plupart des langages familiers, C++ a l’IFNDR, qu’on décrit parfois en plaisantant comme un faux positif à la question « est-ce un programme C++ ? »
      Dans certains cas où le code écrit est manifestement suspect, un compilateur C++ conforme au standard n’a pas le droit de vous le signaler ; il doit simplement continuer et produire quelque chose
      Cela peut être un exécutable qui fonctionne, ou un exécutable qui provoque une catastrophe tous les vendredis. Impossible de le savoir
      La norme ISO identifie bien ces cas, mais c’est tellement vague qu’il est difficile de cerner précisément ce qui est inclus ; à mon avis, la plupart des logiciels C++ non triviaux actuels sont probablement de l’IFNDR en pratique. Autant rejeter tout le langage
  • Rust semble avoir enfin brisé l’idée selon laquelle le programmeur doit contrôler et avoir conscience de tout ce que fait le compilateur
    En réalité, ce n’était déjà plus le cas depuis des décennies, et les compilateurs tenaient presque de la magie. Avec l’emprunt, Rust a largement inversé cette tendance et a rendu les gens plus à l’aise avec l’idée que le compilateur en sait plus qu’eux
    J’aimerais qu’on aille encore plus loin dans ce confort. Sauf nécessité algorithmique, on ne devrait pas avoir à itérer explicitement sur les collections depuis le début. Plusieurs opérations devraient être parallélisées implicitement. J’aimerais un Bash dans l’esprit de Rust

    • C’est exactement l’inverse de mon expérience avec Rust
      Rust est assez transparent sur ce qu’il fait et très conservateur en matière de magie du compilateur. Le langage ne fait pas d’allocation sur le tas, pas de comptage de références, et pas de conversions implicites de types numériques
      Il ne copie pas les types qui ne sont pas déclarés implicitement copiables, et même là, ce n’est légal que pour les types copiables par un simple memcpy superficiel
      Rust utilise partout des abstractions sans surcoût, ce qui rend le code généré prévisible et généralement simple. La disposition de base des types standard est bien connue, donc on sait qu’un parcours de Vec sera compilé en boucle incrémentant un pointeur, et il n’y a pas de parallélisme implicite
      Décrire l’emprunt comme « le compilateur en sait plus que le programmeur » est étrange. L’emprunt ressemble à la vérification de types. Si l’on déclare un type comme temporaire et qu’on l’utilise comme s’il vivait longtemps, on obtient une erreur
      C’est comme retourner un Bar depuis une fonction déclarée comme retournant une structure Foo : cela produit une erreur. Si le compilateur « en sait plus », c’est simplement parce qu’on a écrit un bug
      L’emprunt se compile lui aussi en utilisation directe de pointeurs sans ramasse-miettes, et dans les structures et fonctions à ABI C, il est garanti comme étant littéralement identique aux pointeurs C. Si l’on pense en savoir plus que le compilateur, on peut aussi contourner les durées de vie avec unsafe
    • Il existe déjà des crates qui fournissent des itérateurs parallèles. Il suffit de changer le nom de l’appel à iter()
      En revanche, je ne suis pas d’accord pour dire que cela devrait être implicite
    • Dire qu’on « ne contrôle pas ce que fait le compilateur » est un peu exagéré
      À certains égards, Rust donne plus de contrôle au programmeur que C. Par exemple, Rust prend en charge l’assembleur inline dans le standard, tandis que l’assembleur inline en C dépend d’extensions propres aux fournisseurs
      En revanche, les valeurs par défaut pratiques sont très différentes. En Rust, les conversions de types non sûres exigent beaucoup de procédures et de prudence, et il faut respecter davantage de règles qu’en C
      En particulier, les références Rust se comportent pratiquement toutes comme restrict, et il est très facile de casser cela lorsqu’on fait un cast unsafe d’un pointeur brut vers une référence sûre. Résultat : si l’on a le choix, on est fortement incité à ne pas écrire ce genre de code
    • En tant que personne qui écrit beaucoup d’unsafe Rust, y compris des choses comme des pointeurs tagués, je ne suis pas du tout d’accord
      Au contraire, je veux un contrôle plus explicite, et j’aimerais réduire cette charge grâce à un système de types plus expressif. Idéalement, j’aimerais que le système de types de Rust ressemble à une variante de Prolog
    • Un « Bash dans l’esprit de Rust » serait un Bash avec des types, notamment des flottants, peu de cas exceptionnels, des fonctions à paramètres explicites et des flags de ligne de commande simples
  • Apprendre quand investir dans les contraintes de type, et quand ne pas le faire, est une leçon importante
    Ce n’est pas propre à Rust, même si la manière de l’exprimer peut varier un peu
    J’ai eu affaire à du C++ excessivement typé et à du Java trop abstrait et trop typé ; les deux posent le même genre de problèmes lors du refactoring
    À l’inverse, j’ai aussi vu beaucoup de Go manquant de types et de documentation, où certaines valeurs se retrouvent dispersées partout et deviennent des mines à retardement à l’exécution, ce qui peut rendre le refactoring vraiment très difficile
    La sensation de progresser arrive plus vite au début, mais on finit généralement par livrer les bugs aux utilisateurs
    Il n’existe pas de réponse magique à ce compromis. Rust offre une palette de choix assez large sur cet axe

  • Dire que « Rust vous crie dessus toute la journée, tous les jours, pour des choses qui, dans votre vie d’avant, vous auraient paru parfaitement normales » vaut aussi, dans une certaine mesure, pour un bon compilateur C avec tous les flags activés
    J’aime les langages et compilateurs qui permettent de désactiver sélectivement ces cris et d’écrire volontairement du mauvais code. Du mauvais code qui fonctionne et qu’on peut écrire vite vaut souvent mieux qu’un code parfait qui prend une éternité
    On peut commencer par faire une preuve de concept mauvaise mais fonctionnelle, puis la corriger pour la rendre moins mauvaise
    Le fait de « bien attirer de nouveaux utilisateurs, sans que les bibliothèques ou les outils s’améliorent de façon spectaculaire, et en ne produisant que des forks ponctuels pour traiter des cas d’usage spécifiques » n’a rien à voir avec l’âge
    Attirer des développeurs cœur est difficile, et il faut beaucoup d’efforts pour rendre cela attractif. En plus, les conventions culturelles sont fixées par les premiers adoptants, et l’absence de conventions est souvent aussi néfaste que de mauvaises conventions
    Regardez Python : son approche relâchée des environnements de développement et d’exécution a abouti à une cinquantaine de façons concurrentes de développer ou d’exécuter des programmes Python
    Le dépôt de paquets le plus utilisé, PyPI, a été un bazar pendant des années ; peu de gens construisent par-dessus les paquets existants, les noms ressemblent à ceux d’un générateur de mots aléatoires, l’écosystème contient beaucoup de code malveillant, et on ne peut même pas rechercher des paquets en ligne de commande
    Ce n’est pas la faute du langage, mais celle de la communauté et de l’équipe cœur, restées en spectatrices. La culture est plus importante que la technologie au centre
    Je ne cherche pas à m’en prendre uniquement à Python ; c’est simplement le problème que je connais le mieux. C existe depuis un demi-siècle, mais sa communauté n’a pas non plus vraiment structuré la moitié des solutions mises en place par des langages plus modernes

    • Rust le prend aussi en charge. Il suffit de tout marquer comme unsafe
    • Il est peu probable que Linux et Windows aient commencé à porter des composants en Rust au motif qu’il n’existerait « pas de bibliothèques améliorées »
    • J’ose le dire : cela ressemble beaucoup à TypeScript
      Le compilateur vous crie dessus pour corriger les choses, alors que vous voulez juste tester une idée avant de tout peaufiner parfaitement