Essayons d'avancer des arguments contre Rust

J'ai récemment lu un article critiquant Rust. Même s'il y avait beaucoup de bonnes choses à ce sujet, je ne l'ai pas aimé - trop, c'est très controversé. Dans l'ensemble, je ne peux pas recommander de lire un article critiquant Rust du tout. Ce n'est pas bon, car il est important de discuter des lacunes, et la diffamation des critiques de mauvaise qualité et ineptes, malheureusement, fait ignorer les très bons arguments.



Alors, je vais essayer d'argumenter contre Rust.



Toutes les programmations ne sont pas systématiques



Rust est un langage de programmation système. Il fournit un contrôle précis sur la composition des données et le comportement d'exécution du code au moment de l'exécution pour des performances et une flexibilité maximales. Contrairement à d'autres langages de programmation système, il assure également la sécurité de la mémoire - les programmes bogués se terminent d'une manière bien définie, empêchant un comportement indéfini (potentiellement dangereux).



Dans la plupart des cas, cependant, des performances ou un contrôle absolus sur les ressources matérielles ne sont pas nécessaires. Pour ces situations, les langages gérés modernes tels que Kotlin ou Go offrent une vitesse décente, des performances enviables et une sécurité de la mémoire grâce à l'utilisation d'un ramasse-miettes géré dynamiquement en mémoire.



Complexité



Le temps du programmeur coûte cher, et dans le cas de Rust, vous devez passer beaucoup de temps à apprendre la langue elle-même. La communauté a travaillé dur pour créer du matériel pédagogique de haute qualité, mais la langue est très large. Même s'il est rentable pour vous de réécrire le projet dans Rust, l'apprentissage de la langue elle-même peut être trop coûteux.



Le prix d'un contrôle amélioré est la malédiction du choix:



struct Foo     { bar: Bar         }
struct Foo<'a> { bar: &'a Bar     }
struct Foo<'a> { bar: &'a mut Bar }
struct Foo     { bar: Box<Bar>    }
struct Foo     { bar: Rc<Bar>     }
struct Foo     { bar: Arc<Bar>    }


Dans Kotlin, vous écrivez un cours Foo(val bar: Bar)et commencez à résoudre un problème. Dans Rust, vous devez faire des choix, parfois importants, avec une syntaxe particulière.



Toute cette complexité pour une raison - nous ne savons pas comment créer un langage de bas niveau plus simple et plus sûr pour la mémoire. Mais toutes les tâches ne nécessitent pas un langage de bas niveau.



Voir aussi la présentation Pourquoi le C ++ reste à flot quand le Vaza Sank .



Temps de compilation



Le temps de compilation est un facteur universel. Si un programme dans une langue est lent à s'exécuter, mais que ce langage permet une compilation rapide, alors le programmeur aura plus de temps pour optimiser pour accélérer le lancement du programme!



Dans le dilemme des génériques, Rust a délibérément choisi des compilateurs lents. Cela a du sens (le temps d'exécution s'accélère vraiment), mais vous devrez vous battre dur pour des temps de construction raisonnables sur des projets plus importants.



rustcimplémente probablement l'algorithme de compilation incrémentielle le plus avancé dans les compilateurs de production, mais c'est un peu comme lutter contre le modèle de compilation intégré du langage.



Contrairement au C ++, l'assemblage Rust n'est pas parallélisé à la limite, le nombre de processus parallèles est limité par la longueur du chemin critique dans le graphe de dépendances. La différence sera perceptible si vous avez plus de 40 cœurs à compiler.



Dans Rust, il n'y a pas non plus d'analogues pour l'idiome pimpl , donc changer la caisse nécessite de recompiler (et pas seulement de relier) toutes ses dépendances inverses.



Maturité



Cinq ans est définitivement une courte période, donc Rust est une jeune langue. Si l'avenir s'annonce prometteur, il est plus probable que dans dix ans nous programmerons en C plutôt qu'en Rust (voir l'effet Lindy ). Si vous écrivez des logiciels depuis des décennies, vous devriez sérieusement considérer les risques liés au choix de nouvelles technologies (bien que choisir Java plutôt que Cobol pour les logiciels bancaires dans les années 90 se soit avéré être le bon choix rétrospectivement).



Il n'y a qu'une seule implémentation complète de Rust, le compilateur rustc . Implémentation alternative mrustc la plus avancéesaute délibérément de nombreux contrôles de sécurité statiques. Actuellement, rustc ne prend en charge qu'un seul backend prêt pour la production, LLVM. Par conséquent, la prise en charge des architectures de processeur est ici plus restreinte que C, qui a une implémentation GCC, ainsi que la prise en charge d'un certain nombre de compilateurs propriétaires spécifiques aux fournisseurs.



Enfin, Rust n'a pas de spécification officielle. La spécification actuelle est incomplète et ne documente pas certains détails mineurs d'implémentation.



Alternatives



Outre Rust, il existe d'autres langages pour la programmation de systèmes, notamment C, C ++ et Ada.



Le C ++ moderne fournit des outils et des directives pour améliorer la sécurité. Il existe même une proposition de sécurité à vie des objets de style Rust! Contrairement à Rust, l'utilisation de ces outils ne garantit pas qu'il n'y a pas de problèmes de sécurité de la mémoire. Mais si vous prenez déjà en charge une grande quantité de code C ++, il est logique de vérifier, peut-être que suivre les recommandations et utiliser des désinfectants aidera à résoudre les problèmes de sécurité. C'est difficile, mais clairement plus facile que de réécrire tout le code dans une autre langue!



Si vous utilisez C, vous pouvez appliquer des méthodes formelles pour prouverpas de comportement indéfini, ou juste tout tester à fond .



Ada est sans danger pour la mémoire à moins d'utiliser la mémoire dynamique (ne jamais appeler free).



Rust est un langage intéressant de coût-sécurité, mais loin d'être le seul!



Un ensemble d'outils



Les outils de Rust ne sont pas parfaits. La boîte à outils de base, le compilateur et le système de construction ( cargo ) sont souvent considérés comme les meilleurs de leur catégorie.



Mais, par exemple, certains outils liés à l'exécution (principalement pour le profilage de tas) manquent tout simplement - il est difficile de penser à l'exécution si l'outil n'est tout simplement pas là! En outre, la prise en charge de l'EDI est également bien inférieure au niveau de fiabilité de Java. La refactorisation complexe automatisée d'un programme avec des millions de lignes n'est tout simplement pas possible dans Rust.



L'intégration



Quelles que soient les promesses de Rust, le monde de la programmation de systèmes d'aujourd'hui parle C et C ++. Rust n'essaie pas intentionnellement d'imiter ces langages - il n'utilise pas de classes de style C ++ ou C ABI.



Cela signifie que des ponts doivent être construits entre les mondes. L'intégration ne sera pas transparente, non sécurisée, pas toujours rentable et nécessite une synchronisation entre les langues. Bien que l' intégration fonctionne par endroits et que l' outillage converge, il y a des obstacles occasionnels en cours de route en raison de la complexité globale.



Un problème particulier est que la vision du monde trop confiante de Cargo (idéale pour les projets Rust purs) peut rendre difficile l'intégration avec des systèmes de construction plus importants.



Performance



«Utilisation de LLVM» n'est pas une solution universelle à tous les problèmes de performances. Bien que je ne connaisse pas de benchmarks comparant les performances de C ++ et de Rust en général, il n’est pas difficile de penser à des tâches où Rust est inférieur au C ++.



Le plus gros problème est probablement que la sémantique de déplacement de Rust est basée memcpysur la valeur ( au niveau du code machine). D'autre part, la sémantique C ++ utilise des références spéciales à partir desquelles des données peuvent être extraites (pointeurs au niveau du code machine). En théorie, le compilateur devrait voir la chaîne de copies, en pratique ce n'est souvent pas le cas: # 57077 . Un problème connexe est le manque d'allocation de nouvelles données - Rust a parfois besoin de copier des octets vers / depuis la pile, tandis que C ++ peut créer un objet sur place.



Curieusement, le Rust ABI par défaut (qui sacrifiait la stabilité au profit de l'efficacité) fonctionne parfois moins bien que C: # 26494 .



Enfin, alors qu'en théorie le code Rust devrait être plus efficace en raison des informations beaucoup plus riches sur les alias, l'activation des optimisations liées aux alias entraîne des erreurs LLVM et une compilation incorrecte: # 54878 .



Mais, encore une fois, ce sont des exemples rares, parfois la comparaison est dans l'autre sens. Par exemple, Boxchez Rust aucun problème de performance, qui a dans std::unique_ptr.



Un problème potentiellement plus important est que Rust, avec ses définitions génériques, est moins expressif que C ++. Donc, quelques astuces de formule Le C ++ pour les hautes performances ne peut pas être exprimé en Rust avec une bonne syntaxe.



Valeur dangereuse



Peut-être que l'idée est unsafeencore plus importante pour Rust que la propriété et l'emprunt. En séparant toutes les opérations dangereuses en blocs unsafeet fonctions et en insistant pour leur fournir une interface sécurisée de plus haut niveau, il est possible de créer un système qui simultanément:



  1. fiable (le unsafecode non vérifié ne peut pas provoquer de comportement indéfini),

  2. modulaire (différents blocs non sécurisés peuvent être testés séparément).


Il est bien évident que c'est le cas: le fuzzing du code Rust trouve la panique, mais pas les débordements de tampon.



Mais les perspectives théoriques ne sont pas si brillantes.



Premièrement , il n'y a pas de définition d'un modèle de mémoire Rust, il est donc impossible de vérifier formellement si un bloc unsafe donné est valide ou non. Il existe une définition non officielle des «choses sur lesquelles rustc fait ou peut compter» et le travail est en cours sur un vérificateur d'exécution , mais le modèle actuel n'est pas clair. Ainsi, quelque part, il peut y avoir du code non sûr qui fonctionne bien aujourd'hui, mais qui sera déclaré invalide demain et interrompra une nouvelle optimisation du compilateur dans un an.



Deuxièmement, on pense que les blocs dangereux ne sont pas réellement modulaires. Des blocs suffisamment forts et peu sûrs peuvent, en fait, étendre le langage. Ces deux extensions ne font rien de mal isolément l'une de l'autre, mais conduisent à un comportement indéfini lorsqu'elles sont utilisées simultanément: consultez Equivalence observable et code non sécurisé.



Enfin, il existe des erreurs évidentes dans le compilateur .



Voici quelques sujets que j'ai volontairement omis:



  • Économie ("plus difficile à trouver des programmeurs Rust") - Je pense que la section "Maturité" capture l'essence de cette question, qui ne se limite pas au problème de la poule et de l'œuf.

  • Dépendances ("stdlib est trop petit / trop de dépendances partout") - étant donné la qualité de Cargo et des parties associées du langage, je ne vois personnellement pas cela comme un problème.

  • Liaison dynamique ("Rust devrait avoir un ABI stable") - Je ne pense pas que ce soit un argument fort. La monomorphisation est fondamentalement incompatible avec la liaison dynamique, et si vous en avez vraiment besoin, il existe un ABI C. Je pense vraiment que les choses peuvent être améliorées ici, mais il est peu probable que nous parlions de changements spécifiques dans Rust .


Sujet de discussion dans / r / rust .



All Articles