1 points par GN⁺ 2024-09-27 | 1 commentaires | Partager sur WhatsApp
  • Rust part d’un constat critique : malgré ses atouts — sécurité mémoire, performances, types algébriques et Cargo — l’évolution du langage sur le canal stable ralentit, ce qui donne l’impression d’un « produit de première génération » encore inachevé
  • Après l’agrandissement de la communauté, la prise de décision par consensus est devenue un goulot d’étranglement ; des fonctionnalités comme les coroutines, bien qu’implémentées, s’accumulent sans pouvoir être utilisées pendant longtemps dans stable Rust
  • L’hypothétique « seph edition » viserait à conserver l’écosystème Rust et Cargo existant tout en autorisant des breaking changes afin d’expérimenter un système d’effets, des permissions à la compilation, l’emprunt de champs de structures, le comptime, etc.
  • Pour réduire les risques de chaîne d’approvisionnement, il faudrait associer des capabilities aux fonctionnalités sensibles comme l’écriture de fichiers, le réseau, la FFI ou unsafe, et demander à l’appelant de les autoriser explicitement
  • La plupart des propositions ne sont pas compatibles avec Rust actuel ; même l’ajout des seules capabilities créerait de nouvelles conditions d’échec rompant la compatibilité semver, ce qui nécessiterait une nouvelle edition ou un fork du compilateur

Pourquoi Rust donne l’impression d’un « produit de première génération »

  • Au départ, Rust apparaît comme un langage très séduisant grâce à ses types algébriques, à sa sécurité mémoire sans perte de performances et à son gestionnaire de paquets moderne
  • Après environ quatre ans d’utilisation, le langage donne l’impression de rester « toujours un peu incomplet »
  • Le grief central est que les nouvelles fonctionnalités arrivent moins souvent dans stable Rust et que le rythme d’évolution du langage a fortement ralenti
  • Le Rust unstable book recense environ 700 fonctionnalités instables, dont une grande partie concerne des changements de la bibliothèque standard
  • Les coroutines sont une fonctionnalité dont la RFC date de sept ans et qui est implémentée dans le compilateur, mais que les utilisateurs de stable Rust ne peuvent toujours pas employer
  • Le processus RFC de Rust ressemble à un espace où les bonnes idées restent longtemps en suspens
    • La discussion sur l’amélioration de Mutex est citée comme exemple : 25 personnes y ont laissé plus de 200 commentaires pendant deux ans, sans parvenir à une conclusion claire

L’hypothétique « seph edition »

  • L’idée imaginée consiste à forker le compilateur tout en laissant le code Rust existant intact, et à ajouter une Rust edition distincte appelée « seph »
  • Dans cette edition, les breaking changes seraient autorisés, mais tant que le compilateur pourrait continuer à compiler le Rust mainline, les crates existantes de Cargo resteraient utilisables
  • Les axes à modifier seraient principalement au nombre de cinq
    • Les traits de fonctions et le système d’effets
    • Les capabilities à la compilation
    • La refonte de Pin, du move et des emprunts de structures
    • Le comptime à la Zig
    • De petites améliorations de syntaxe et de bibliothèque standard

Traits de fonctions et système d’effets

  • Rust dispose de traits pour les structures, mais la proposition est de pouvoir associer aux fonctions des traits/effects plus riches
  • Les propriétés qu’une fonction peut avoir se résument ainsi
    • Possibilité ou non de paniquer
    • Taille de pile fixe ou non
    • Exécution jusqu’au bout, ou utilisation de yield ou await
    • Type de continuation lorsqu’il s’agit d’une coroutine
    • Fonction pure ou non
    • Exécution indirecte de code unsafe ou non
    • Garantie de terminaison ou non
  • Si les paramètres et le type de retour d’une fonction étaient exposés comme des associated types de la fonction, il deviendrait possible de référencer des types aujourd’hui inaccessibles en stable Rust
  • L’exemple donné est un code qui nomme directement le type de retour d’une fonction, comme some_iter::Output, pour le placer dans un champ de structure
  • Lorsqu’un bloc de code donné doit être garanti comme ne pouvant jamais panic, comme dans le kernel Linux, une annotation du type #[disallow(Panic)] permettrait de vérifier la possibilité de panic, y compris dans les appels récursifs
  • Le compilateur traite déjà des traits liés aux fonctions comme Fn, FnOnce et FnMut, mais la forme actuelle est jugée trop pauvre
  • Pour aller plus loin, un lien renvoie à l’article et à la présentation de Yoshua Wuyts sur les effect systems

Réduire les risques de chaîne d’approvisionnement avec des capabilities à la compilation

  • La plupart des projets Rust importent de nombreuses crates tierces, et même une petite crate utilitaire peut créer un risque de chaîne d’approvisionnement via une mise à jour malveillante
  • De la même façon que unsafe est un opt-in explicite pour la sécurité mémoire, les fonctionnalités sensibles liées au système de fichiers, au réseau, à la FFI ou aux raw pointers devraient devoir être autorisées explicitement
  • La proposition consiste à ajouter des marker tags aux fonctions sensibles de la bibliothèque standard
    • Par exemple, std::fs::write(path, contents) écrit un fichier à un chemin arbitraire et aurait donc un tag du type #[cap(fs_write)]
    • Le compilateur taint automatiquement tout l’arbre d’appels qui invoque cette fonction
  • Si une crate tierce a besoin de la capability fs_write, l’appelant devrait l’autoriser explicitement dans Cargo.toml ou via une annotation au point d’appel
  • Sans autorisation, le compilateur pourrait émettre une erreur indiquant que foo::do_stuff() écrit sur le système de fichiers local, mais que la crate foo n’a pas été approuvée pour cette capability
  • Beaucoup de crates utilitaires comme human-size ou serde n’ont besoin d’aucune capability particulière ; même si leur auteur ajoutait du code malveillant, l’écriture de fichiers ou les actions réseau pourraient être bloquées dès la compilation
  • Une autre approche serait de modifier les API sensibles pour qu’elles prennent un paramètre Capability séparé
    • Par exemple, std::fs::write exigerait une valeur FsWriteCapability
    • La création des objets Capability serait limitée à la root crate
  • Cette approche ajoute du boilerplate mais offre plus de flexibilité, et il faudrait traiter de manière similaire les scripts build.rs et les blocs unsafe
  • Même si crates.io était compromis et qu’un code de cryptolocker était ajouté à serde, l’approche par capabilities provoquerait une erreur de compilation avant que ce code ne s’exécute sur de nombreuses machines de développeurs ou ne soit inclus dans des binaires

Refonte de Pin, du move et de l’emprunt de champs de structures

  • Pin est vu comme un hack complexe destiné à contourner des lacunes du borrow checker, et comme un pansement né de la volonté de préserver la rétrocompatibilité
  • Ce qui serait réellement nécessaire ressemble plutôt à un marker trait Move indiquant les types déplaçables
  • Rust n’a actuellement pas de trait Pin, mais Unpin et !Unpin, et cette double négation rend le concept plus difficile à comprendre
  • Pin ne s’appliquant qu’aux types référence, il entraîne l’apparition de code enveloppé dans des Box un peu partout
    • Sont cités comme exemples des helper libraries comme les wrappers de streams Tokio, ouroboros, async-trait et self_cell
  • Les fonctions qui reçoivent des valeurs pinned, comme Future::poll(self: Pin<&mut Self>, ..), deviennent elles aussi complexes, et des crates séparées sont nécessaires pour gérer la projection
  • À l’intérieur d’une fonction, le borrow checker gère les variables dans des états « owned », « borrowed » ou « mutably borrowed », mais ces états ne sont pas directement visibles par le programmeur
  • Une async fn amène le compilateur à créer une structure cachée pour stocker les états de suspension, ce qui peut produire une situation où un champ en emprunte un autre
  • Rust n’a pas de syntaxe permettant d’exprimer qu’un champ de structure est dans un état borrowed, ni d’exprimer directement les lifetimes entre champs
  • La direction proposée est d’étendre le borrow checker pour permettre d’écrire directement des emprunts de champs de structures
    • Par exemple, une syntaxe d’« emprunt local » comme y: &'Self::x Vec<usize>
    • Le compilateur saurait que x est emprunté et appliquerait les mêmes contraintes qu’aux variables borrowed à l’intérieur d’une fonction
  • Une telle syntaxe pourrait aussi servir pour les structures self-referential ou pour manipuler, dans un AST, des formes comme source: String et ast_nodes: Vec<&'Self::source str>
  • Une structure ayant un champ borrowed ne pouvant pas être déplacée, elle n’implémenterait pas Move, et l’on pourrait même envisager un trait Mover pour permettre des déplacements sûrs par soi-même

Le comptime à la Zig et le problème des macros Rust

  • Le compilateur Rust semble en pratique gérer plusieurs langages à la fois : Rust, le langage de macros Rust et les proc macros
  • Le point central de la critique est que Rust lui-même est bon, mais que le langage de macros ne l’est pas
  • Le comptime de Zig consiste à faire exécuter certaines parties du code au moment de la compilation par un petit interpréteur intégré au compilateur
  • Fonctions, paramètres, if et boucles peuvent être marqués comme code de compilation, tandis que le code non-comptime est émis dans le programme réel
  • L’étude de cas std print de Zig montre un exemple où la format string est reçue comme paramètre comptime, puis analysée dans une boucle comptime afin de générer le code d’affichage
  • L’implémentation de println!() en Rust appelle des fonctions internes comme format_args_nl, que l’auteur suppose hardcodées dans le compilateur
  • La critique se poursuit en disant que même les auteurs du compilateur Rust semblent ne pas vouloir utiliser le langage de macros de Rust

Petites propositions de modification de la syntaxe et de la bibliothèque standard

  • Un point évoqué est de corriger le fait que Range<T> implémente Copy lorsque T: Copy
  • Le problème de derive avec les associated types est également cité comme cible de correction
  • Une proposition est que les expressions if let prennent en charge le AND logique
    • Forme souhaitée : if let Some(x) = some_var && some_expr { }
    • Le contournement actuel consiste à faire un pattern matching sur le tuple (some_var, check_foo()), mais même si some_var vaut None, check_foo() est exécutée, ce qui diffère du comportement en short-circuit d’un if classique
    • Exemple : Playground
  • L’ergonomie des raw pointers est aussi citée comme amélioration souhaitable
    • Avec les références, on peut écrire myref.x, alors qu’avec les pointers il faut écrire (*myptr).x ou (*(*myptr).p).y
    • La position défendue est que le code unsafe devrait être aussi facile que possible à lire et à écrire
  • Une proposition est de modifier les types de collections intégrés afin qu’ils reçoivent explicitement un Allocator dans leur constructeur, au lieu d’utiliser l’allocateur global
  • async aurait également besoin d’améliorations, mais le sujet est laissé de côté car il demanderait un article séparé

Compatibilité et faisabilité

  • La plupart des propositions ne sont pas compatibles avec Rust existant
  • Même l’ajout de security capabilities introduirait une nouvelle forme de condition d’échec rompant la compatibilité semver des crates, ce qui nécessiterait une nouvelle Rust edition
  • Il y a quelques années, l’auteur aurait peut-être rédigé une RFC, mais il dit vouloir éviter qu’une nouvelle idée non réalisée s’enlise dans un long fil de commentaires GitHub autour d’une RFC
  • Il en revient donc finalement à l’idée de forker directement le compilateur, tout en restant confronté à la contrainte réaliste d’avoir trop de projets à mener

1 commentaires

 
GN⁺ 2024-09-27
Avis sur Hacker News
  • Contrairement à l’idée selon laquelle le processus de RFC de Rust serait un cimetière de bonnes idées, je pense que l’équipe cœur de Rust a raison de rendre difficile l’ajout de nouvelles fonctionnalités au langage
    Il faut éviter que la surface du langage devienne boursouflée, incohérente et imprévisible. J’aimais aussi Swift au début, mais j’ai fini par abandonner à mesure que s’accumulaient des noms de fonctions redondants comme isMultiple(of:), des règles d’analyse des accolades pour SwiftUI, des règles sur les types référence/valeur et la mutabilité, ou encore la notation abrégée des arguments de closures. Les bonnes idées sont courantes, donc j’aimerais que Rust reste aussi léger que possible

    • Je suis l’auteur du billet original. Quand on utilise Rust, on tombe souvent sur des trous où la fonctionnalité X et la fonctionnalité Y sont prises en charge séparément, mais ne peuvent pas être utilisées ensemble
      Par exemple, on peut écrire une fonction qui retourne impl Trait, et une structure peut avoir des champs arbitraires, mais on ne peut pas mettre dans un champ de structure une valeur retournée sous forme de impl Trait, parce qu’on ne peut pas nommer son type. if a && b fonctionne, tout comme if let Some(x) = x, mais pas if let Some(x) = x && b. J’aimerais que ce genre de choses soit corrigé. En nombre de lignes de code du compilateur, Rust grossirait, mais du point de vue de la complexité à apprendre et à utiliser, ces trous de fonctionnalités rendent au contraire le langage plus complexe. Pin n’a pas été un énorme travail à implémenter dans la bibliothèque standard, mais ce n’est absolument pas une fonctionnalité légère, et la charge cognitive est immense. Je préfère un borrow checker complexe et du code Rust facile à lire à un compilateur simple et un langage difficile à utiliser
    • Écrire une RFC est plus facile que l’implémenter, et il y a aussi davantage de personnes capables d’écrire des RFC. Donc, quel que soit le niveau de popularité, la file d’attente des RFC donnera forcément l’impression d’être un cimetière de bonnes idées
      Même si 100 % des idées en attente étaient acceptées et finissaient par être traitées, cela donnerait cette impression à cause de l’écart entre le rythme des entrées et celui des sorties. Si vous voulez prendre de l’avance sur les auteurs de RFC, mieux vaut ne pas écrire de RFC, mais produire une implémentation prête pour la production, avec documentation et tests, pouvant être fusionnée proprement dans l’arbre
    • Je suis globalement d’accord, mais les constantes associées génériques des traits sont dans un état paradoxal : elles sont stabilisées, mais ne peuvent pas être utilisées comme de vraies constantes
      On ne peut les utiliser ni comme const generics d’autres types, ni comme tailles de tableaux. Si l’on a simplement besoin d’une valeur, il suffit de définir une fonction qui la retourne, donc en l’état ce n’est pas très utile. Si c’était correctement pris en charge, on pourrait se débarrasser de crates auxiliaires comme generic_array et typenum dans les bibliothèques de cryptographie. Cela dit, je suis d’accord sur le fait que l’équipe Rust doit rester prudente dans l’ajout de fonctionnalités
    • Parmi les langages de programmation déjà plus ou moins mainstream, Rust est un peu dans l’état du wagyu : il ne faut pas lui rajouter tellement de gras qu’il en devienne immangeable
    • Les bonnes idées sont, par définition, rares et précieuses
  • La situation des dépendances est assez grave, mais il semble que peu de gens veuillent l’admettre. Un exemple que j’ai vu récemment est le crate cargo-watch
    À la base, c’est une application simple qui surveille les changements de fichiers et relance le compilateur, avec une implémentation de moins de 1 000 lignes. Mais si l’on vendore ses dépendances, on arrive à près de 4 millions de lignes de code Rust réparties sur plus de 8 000 fichiers. C’est excessif pour un simple surveillant de fichiers
    https://crates.io/crates/cargo-watch

    • Si l’on rend les dépendances transitives faciles à créer, et qu’il existe une culture du « s’il y a une bibliothèque, il faut forcément l’utiliser », on en arrive inévitablement là
      C/C++ est presque le seul cas, parmi les langages largement utilisés, où un gestionnaire de paquets à la npm n’est pas populaire ; la plupart des bibliothèques y sont donc autonomes, ou ont peu de dépendances, souvent optionnelles. efsw est un surveillant de système de fichiers en C++ de 7 000 lignes sans dépendance. Les bibliothèques single-header du monde de la programmation de jeux, comme stb_*, cgltf, ainsi que Dear ImGui, ont été parmi les plus agréables que j’aie utilisées. Pour un nouveau gestionnaire de paquets, je pense qu’interdire les dépendances transitives pourrait être globalement bénéfique. Les grosses dépendances de bibliothèques peuvent être installées directement par l’utilisateur, remplacées par des callbacks, ou intégrées comme fonctionnalités standard
      https://github.com/SpartanJ/efsw
      https://github.com/nothings/stb
      https://github.com/jkuhlmann/cgltf
      https://github.com/ocornut/imgui
    • Aujourd’hui, la jungle des dépendances est la façon normale de mener les choses à terme. Le mieux qu’un runtime puisse faire est de l’accepter, de la rendre aussi rapide et sûre que possible, et d’élargir la bibliothèque standard pour prendre aussi en charge les projets avec un minimum de dépendances
      Un surveillant de fichiers, surtout s’il doit prendre en charge plusieurs plateformes, n’est jamais vraiment simple, à mon avis
    • C’est un phénomène naturel, et pas si effrayant. Tout code repose sur une chaîne de montagnes de dépendances qui contient bien plus de fonctionnalités que ce qu’il utilise réellement
      Une partie de cargo watch importe une bibliothèque wrapper de l’API Win32, qui est constituée de bindings générés automatiquement pour les appels Win32 et est donc forcément énorme. Les bibliothèques standard de la plupart des langages comptent aussi des millions de lignes, dont une application n’utilise qu’une partie. Boost en C++ est monstrueusement gros, mais les développeurs n’en utilisent que quelques extensions. Je préfère même un petit nombre de grosses dépendances maintenues et utilisées par beaucoup de monde à l’enfer façon npm où un paquet comme isOdd ou is even peut casser tout un écosystème ; Rust est globalement plus proche du premier cas
    • Au travail, nous supprimons et réécrivons consciemment plusieurs dépendances, mais 1 000 lignes comme 4 millions de lignes me semblent toutes deux être de mauvaises estimations du nombre de lignes de code réellement approprié pour ce projet
      La plupart des dépendances de cargo-watch semblent venir de trois exigences directes : clap, cargo_metadata et watchexec. clap tire beaucoup d’éléments CLI spécifiques aux plateformes, et cargo_metadata doit forcément importer beaucoup de choses de l’écosystème serde. watchexec pourrait être amélioré : il dépend de command-group, maintenu par la même organisation, qui exige inconditionnellement Tokio. Le problème plus large est qu’il est difficile de corriger facilement les dépendances excessives d’autres crates. Si un crate est bloqué sur une ancienne version de watchexec, on peut parfois s’en sortir en ajustant Cargo.lock, mais c’est généralement difficile et il faut contourner avec [patch]. Ce serait bien d’avoir un moyen simple de définir un « stand-in » pour remplacer une version précise d’un crate, mais cela ressemble à un gros sujet de recherche pour les gestionnaires de paquets existants
    • La majeure partie du nombre de lignes vient probablement des crates d’API Windows générés. Ils sont notoirement énormes
  • Rust n’est plus un nouveau langage excitant ; il est dans une phase qui vise une adoption à grande échelle. Il est naturel et sain que le développement de fonctionnalités ralentisse
    Aujourd’hui, les erreurs de conception sont bien plus dommageables qu’un rythme lent. Ce qui rend Rust intéressant, ce ne sont pas ses fonctionnalités cool, mais le fait qu’il représente une nouvelle catégorie de langage : sûr côté mémoire, sans garbage collection, et prêt pour la production. Le voir réellement adopté là où c’est important est plus intéressant que de rendre le langage encore meilleur, et cette adoption est facilitée par la confiance dans une gouvernance prudente

    • JavaScript peut être vu comme étant à un stade similaire de son cycle de vie, mais il a tout de même ajouté une foule de nouvelles fonctionnalités excellentes ces dix dernières années
      L’opérateur spread, les fonctions génératrices, async, les fonctions fléchées, leftpad, le nouveau Date, etc. : la liste des fonctionnalités importantes est sans fin. JS est bien plus ancien et bien plus utilisé que Rust, avec plusieurs implémentations en production qui doivent toutes implémenter les nouvelles fonctionnalités de manière cohérente. Il y a eu une période de stagnation autour d’ES5, mais la différence semble être que le comité de standardisation ECMAScript s’est ressaisi
    • Ada/SPARK existe depuis longtemps, donc je me demande si c’est vraiment une nouvelle catégorie de langage. SPARK pousse la sécurité encore plus loin, jusqu’à inclure la vérification formelle
  • Les Rustaceans sont tellement obsédés par l’idée de tout réécrire en Rust que, pour un article sur la réécriture de Rust, j’ai sincèrement cru que ce serait une blague méta-satirique

    • C’est déjà arrivé à la préhistoire. À l’origine, Rust était écrit en OCaml, mais il a fini par être réécrit en Rust
    • Je suis l’auteur de l’article original. C’était précisément la référence visée par le titre
    • Ils veulent vous faire, ou nous faire, tout réécrire en Rust. Pas le faire eux-mêmes
    • Les gens des langages de programmation aiment aussi le bootstrapping. Écrire Rust en Rust n’est pas si absurde
  • Il est un peu étrange de commencer par se plaindre de la lenteur des décisions, puis d’énumérer comme raisons de non-stabilisation des fonctionnalités qui n’ont pas grand-chose à voir avec la prise de décision
    Par exemple, les coroutines sont bloquées parce qu’il existe des cas limites difficiles à résoudre correctement. Il n’y a pas, dans le compilateur, une implémentation terminée qu’il suffirait « d’activer » : c’est une implémentation inachevée qui fonctionne dans beaucoup de cas, mais qu’on ne peut pas activer dans la version stable. Pour les traits de fonctions aussi, il a été explicitement décidé de ne pas les stabiliser sous leur forme actuelle, pour plusieurs raisons techniques et à cause de leurs interactions avec de futures fonctionnalités. Si l’on revenait en arrière, certaines choses seraient conçues différemment, mais la plupart sont liées à des décisions prises aux débuts de Rust, quand l’équipe et les ressources étaient beaucoup plus réduites. On peut se dire qu’aujourd’hui de meilleurs choix seraient possibles, et envisager une rupture à la façon Rust 2.0, mais le désastre du passage de Python 2 à 3 a été si important que beaucoup préfèrent accepter les aspérités existantes. Quand on lit la newsletter hebdomadaire Rust, on voit que les validations de RFC et les décisions de stabilisation sont traitées chaque semaine. Il y a certes des cas qui prennent trop longtemps, mais les problèmes de personnes, de coordination et de temps sont souvent plus difficiles à résoudre que les problèmes techniques

  • Il faut aussi lire la réponse de Josh Triplett sur Reddit avec cet article. L’un des exemples centraux de l’article, Mutex, est tout simplement faux
    https://old.reddit.com/r/rust/comments/1fpomvp/rewriting_rus...
    Modification : le même commentaire se trouve aussi ici
    https://news.ycombinator.com/item?id=41655268

  • La première chose que j’ai ressentie en apprenant Rust, c’est qu’il semblait déjà contenir presque toutes les fonctionnalités imaginables
    Cela ne veut pas dire que l’équipe Rust n’a jamais refusé quoi que ce soit, mais les gens en veulent toujours davantage. Certaines demandes sont légitimes, mais d’autres donnent l’impression qu’il faudrait absolument ajouter au langage une fonctionnalité que seuls 2 % des développeurs utiliseront, et que seuls 1 % comprendront. Un langage complexe n’a pas besoin de devenir encore plus complexe. Zig est plus simple, probablement plus rapide, et connaît beaucoup moins de drama communautaire. J’aimerais que Zig reçoive davantage de financements

    • Vous seriez peut-être surpris de voir combien de fonctionnalités proposées par des personnes quelconques sont rejetées par la communauté Rust
      Rust ne cherche pas à intégrer toutes les fonctionnalités possibles. Cela dit, si l’on ne voit pas clairement quels problèmes résolvent des choses comme GAT ou TAIT, on peut avoir cette impression. Zig est peut-être un bon langage moderne, mais si l’objectif est la sécurité mémoire, ce n’est pas une option
    • Je suis l’auteur du billet original. À mon avis, le budget de complexité de Rust est souvent dépensé au mauvais endroit
      Par exemple, l’interaction entre Pin et les futures ajoute au langage une complexité absurde, dont une partie me semble inutile. J’aimerais qu’il existe un langage proche de Rust sans Pin du tout. Je pense aussi qu’il y aurait moyen de simplifier le borrow checker, à la fois dans la syntaxe et dans l’implémentation, mais je n’y ai pas encore réfléchi concrètement. À ce stade, ce serait difficile à changer sans forker le langage, mais Rust ne sera pas le dernier langage à utiliser un borrow checker. J’espère que la prochaine génération pourra l’améliorer sans produire un langage encore plus gros
    • Plus de financement peut attirer le mauvais genre de personnes. Leur médiocrité et les dégâts qu’elles peuvent causer ne deviennent parfois visibles que trop tard
    • Je me demande de quel drama de la communauté Rust il est question
      Je vois bien du drama lié à Rust, mais il vient généralement de personnes qui résistent à son utilisation ou à son adoption. Comme la récente agitation autour de Rust for Linux. Je n’ai pas l’impression que ce soit courant au sein de la communauté elle-même, même si j’ai peut-être manqué quelque chose. Zig est excellent, mais il n’est pas encore prêt pour la production
    • Le cimetière des fonctionnalités enfouies dans nightly est en réalité assez vaste. Même des fonctionnalités importantes comme la spécialisation y sont stuck pour toujours
  • L’auteur du commentaire lié a analysé en profondeur les primitives de synchronisation de plusieurs langages, puis a réécrit les primitives de synchronisation de Rust, comme Mutex et RwLock, afin qu’elles utilisent directement les primitives bas niveau de l’OS sur chaque grand système d’exploitation
    En utilisant des mécanismes comme futex sous Linux, il les a rendues plus rapides, plus petites et globalement meilleures, et au passage il a pratiquement écrit le livre sur la programmation parallèle en Rust. Il est aussi utile pour la programmation parallèle hors Rust
    https://www.oreilly.com/library/view/rust-atomics-and/978109...
    Il n’a pas non plus passé 7 ans à simplement s’amuser du côté des coroutines. Il a ajouté les fonctions async, les traits pouvant contenir des fonctions async, ainsi que plusieurs fonctionnalités nécessaires à la standardisation de AsyncWrite et AsyncRead, et il existe aussi une implémentation des générateurs en nightly. La question de savoir s’il faut accepter la complexité de coroutines totalement générales, ou s’arrêter aux générateurs, est encore débattue. Certaines fonctionnalités comme AsyncIterator avancent lentement, mais beaucoup de travaux sont actifs. Il est toujours intéressant de voir une partie des gens dire que le langage est trop lent, tandis que l’autre se plaint qu’il va trop vite
    Les traits de fonctions et le système d’effets ont eux aussi fait l’objet récemment d’une importante exploration de conception, avec l’objectif de trouver une solution pour ne pas avoir à écrire plusieurs fois les fonctions selon chaque combinaison de async, try et const. Le sandboxing des crates malveillantes n’est pas un problème de niveau langage ; il nécessite une combinaison de vérificateur et de sandbox d’exécution, et les composants WebAssembly semblent plus prometteurs. En revanche, il existe un fort intérêt pour les capabilities à la compilation, comme le choix de l’allocateur ou du runtime async, ou l’hypothèse d’une plateforme 64 bits ; le sandboxing des proc macros est souhaité non pas pour empêcher la malveillance, mais pour permettre une mise en cache correcte
    Les structures auto-référentielles ne sont pas un problème de syntaxe, mais un problème très difficile à traiter pour le vérificateur d’emprunts. Les emprunts partiels sont déjà pris en charge dans la capture des closures, mais l’enjeu est de maintenir une gestion de version sémantique stable lorsqu’on les expose dans une API publique. Des pistes comme des « borrow groups » nommés semblent prometteuses. Du côté de comptime, plusieurs directions sont également explorées, et une RFC visant à renforcer macro_rules a récemment été rédigée. L’impl de Range est déjà en cours, associé à un changement incompatible via une édition. La combinaison de if let avec le AND logique existe comme fonctionnalité instable et est proche de la stabilisation. Plusieurs propositions ont aussi été faites pour améliorer la syntaxe d’accès aux champs via pointeur, mais la question reste ouverte de savoir si élargir encore la surface du langage est un gain ou une perte. Grâce aux éditions de Rust, beaucoup de choses peuvent être modifiées si la conception est suffisamment convaincante. Les 700 fonctionnalités instables sont un vrai problème : il faut un gros travail de nettoyage de celles qui ont peu de chances d’être stabilisées

    • C’est ainsi qu’il faut développer les packages de base au niveau de la bibliothèque standard. Les langages que nous utilisons aujourd’hui le seront encore pendant des décennies, et prendre maintenant des décisions lentes mais bonnes fera gagner beaucoup plus de temps plus tard
    • Il y a eu beaucoup de discussions sur le sandboxing des proc macros et des scripts de build
      Des macros plus déclaratives, la délégation de la logique des crates -sys à des bibliothèques partagées, ainsi que cfg(version) / cfg(accessible) réduiraient fortement le besoin d’implémentations côté utilisateur, mais le runtime resterait toujours là. Plus j’y pense, plus les ACL de cackle me semblent être une bonne approche extensible pour suivre le travail des proc macros, des scripts de build et du code runtime, et pour auditer l’usage de l’arbre de dépendances. J’ai entendu dire que cargo-redpen évoluait aussi vers un outil d’audit des appels, mais j’imagine quelque chose de plus haut niveau, comme cackle
      https://github.com/cackle-rs/cackle
    • Je suis l’auteur du billet original. C’est agréable d’avoir un point de vue interne détaillé
      Je comprends que les gens se plaignent que Rust est un gros langage et ne veulent pas qu’il grossisse encore, mais conserver l’implémentation async à moitié cuite actuelle ne rendra pas le langage plus petit ni plus simple. Cela rend simplement le langage moins bon. Pour les emprunts partiels aussi, ce serait déjà bien que cela fonctionne au moins au sein d’une même crate, même si ce n’est pas dans une API publique. C’est un problème que l’on rencontre sans cesse en programmation réelle. Je me demande aussi pourquoi le sandboxing des crates malveillantes serait impossible au niveau du langage. Si une fonction n’a pas de code tiers unsafe dans son arbre d’appels et n’effectue pas d’appels système, elle ne peut manipuler que les arguments qui lui ont déjà été passés, les variables locales et les globales dans sa portée. Si l’on pouvait renforcer ce mur pour en faire une frontière de sécurité, cela semblerait réduire fortement les risques liés à la chaîne d’approvisionnement des dépendances
  • La mission de Rust a toujours été difficile dès le départ : mêler performance, sécurité et expressivité. Avec le retrait de Mozilla, il a perdu son mode fondateur, et comme la plupart des membres de l’équipe centrale d’origine sont aussi partis, il n’est pas surprenant que les progrès aient ralenti
    Personnellement, je trouve cela préférable à prendre une mauvaise direction

  • Si l’on réécrivait Rust, j’imagine qu’on irait plutôt vers moins de fonctionnalités que vers davantage
    À la manière de QBE par rapport à LLVM, ce serait fournir 70 % de Rust avec 10 % du code. En retirant les macros et certaines fonctionnalités rarement utilisées, ce serait peut-être possible
    https://c9x.me/compile/

    • Sans vouloir dénigrer QBE, son objectif initial était de fournir 90 % des performances avec 10 % du code, avant que cela ne devienne 70 % par la suite
      Qu’il s’agisse de Rust ou d’autre chose, il est difficile de savoir à quel point c’est faisable avant de vraiment essayer