Journal Sortie de Rust 0.7

51
12
juil.
2013

Début juillet sortait la version 0.7 du langage de programmation Rust.

Ayant pour le moment écrit la faramineuse quantité de 0 lignes de code en Rust, je ne me lancerai pas dans l'écriture d'une dépêche (qui n'atteindrait pas les standards de kalitay de linuxfr). En revanche je m'intéresse beaucoup à ce langage depuis quelques mois, et si je peux susciter l’intérêt de quelques lecteurs alors mon but sera atteint.

Rust est un langage crée par Mozilla, qui s'en sert pour écrire une moteur de rendu HTML, Servo, qui pourrait un jour remplacer Gecko.

Au premier abord, il ressemble au langage Go de Google
- un langage "système", compilable en code natif avec des performances proches du C (je reviendrai sur ce point), mais qui gère automatiquement la mémoire.
- typage statique et fort, avec de l'inférence de type, permettant de rendre l'écriture de code proche des langages à typage dynamique.
- concurrence gérée de base, favorisant des tâches communiquant via des messages.
- langage objet mais qui s'éloignent du modèle C++/java (arborescence de classes).

Pourtant ces 2 langages sont au final très différents. Go se veut un langage très simple, qui n'apporte pas vraiment de concept nouveau, mais se "contente" de déployer les bonnes pratiques connues. Rust pour sa part se veut bien plus ambitieux, même si c'est au prix d'une complexité plus grande, et aussi d'une gestation bien plus longue !

Rust n'est pas juste un langage impératif, mais possède aussi des caractéristiques fonctionnelles (closures, types avancés). Il y aussi un système de macro qui semble puissant. Mais le plus fascinant reste sa gestion de la mémoire et des "pointeurs".

Il y a 3 types de références :
- managed: objets gérés par un garbage collector.
- owned: objets alloués sur la pile quand c'est possible, sur le tas sinon (c'est le compilateur qui choisit). Ces objets sont désalloués automatiquement à la fin de leur portée.
- borrowed: c'est cette catégorie qui fait une bonne partie de la puissance du langage. Il s'agit d'une référence à un objet owned, mais dont la durée de vie est forcément plus courte que celle de la référence originale. Le compilateur va vérifier statiquement les contraintes de durée de vie (une grande partie de l'intelligence du compilateur est son borrow checker), et va permettre de garantir qu'on ne peut pas avoir de référence vers un objet désalloué.

Il y a aussi une sémantique de move, qui permet par exemple à une tâche de passer la responsabilité d'une référence à une autre tâche, et donc d'être sûr à la compilation que celle-ci ne peuvent pas écrire la même zone mémoire en même temps (avec les problèmes que ça entraîne). Cela permet aussi à chaque tâche de gérer son propre pool mémoire.

Au final on obtient un langage qui ne permet pas les pointeurs nuls, mais qui permet de se passer bien souvent du garbage collector ou de l'allocation sur le tas. D'ailleurs l'objectif est de se passer du GC dans toute la bibliothèque standard (je ne sais plus si c'est pas déjà le cas ou pas).

Le langage est en développement actif, et n'hésite pas à casser la compatibilité entre les versions (ce qui est une bonne chose, tant que la 1.0 n'est pas atteinte en tout cas). Par exemple rien que la sémantique du 'for' n'est pas encore fixée !

En 0.6, on avait quelque chose proche de Ruby:

for [1,2,3].each |i| { /* closure */ }

En 0.7, on passe d'une itération interne à une itération externe, jugée plus souple et puissante, proche de Python. Le résultat est un peu bâtard:

for [1,2,3].iter().advance |i| { /* block */ }

Le but étant en 0.8 de pouvoir écrire quelque chose du genre (c'est encore en réflexion je crois):

for i in [1,2,3] { /* block */ }

Je trouve ça vraiment intéressant que Mozilla teste en grandeur nature son langage (en le cassant régulièrement) pour écrire un vrai gros logiciel (Servo que j'ai cité au début). J'imagine qu'une telle démarche a déjà été faite, mais je ne saurai dire pour quel langage.

Au dernières nouvelles, le langage est encore loin des performances du C (genre 5 fois plus lent), mais il est encore jeune ; si je me souviens bien Go a mis du temps atteindre ses performances actuelles. Il y a encore de nombreux progrès à prévoir, mais certaines nouvelles sont encourageantes, comme le fait que Rust peut donner des informations à LLVM qui permettent d'optimiser le binaire, et que la sémantique du C ne permet pas (je ne remets pas la main sur le lien, je ne retrouve que ça).

Il y aurait encore beaucoup à dire (mais j'ai peur d'avoir déjà été indigeste). Pour ma part je suis l'actualité sur la page reddit de Rust (via le flux RSS). Voici 2 articles passionnants que j'ai pu y trouver :

Je place beaucoup d'espoir dans ce langage, qui ressemble beaucoup à ce que j'attends depuis des années.

  • # Comparaison entre Go et Rust

    Posté par (page perso) . Évalué à 10.

    Il y a eu également une série d'articles écrits par Neil Brown et publiés sur LWN pour comparer les langages Go et Rust et comprendre leur philosophies respectives :

    Go and Rust — objects without class => https://lwn.net/Articles/548560/
    Little things that matter in language design => https://lwn.net/Articles/553131/
    Philosophy and "for" loops — more from Go and Rust => https://lwn.net/Articles/557073/

  • # Go sans concept nouveau

    Posté par (page perso) . Évalué à 3.

    Go se veut un langage très simple, qui n'apporte pas vraiment de concept nouveau

    Go se démarque de la plupart des langages par le fait que:
    - l'ordonnancement des threads (goroutines) est géré par le runtime, et non pas par l'OS. Ça permet d'avoir des centaines de millier des goroutines en même temps sans que les perfs s'écroulent. Par contre ça pose des problèmes dans certains cas particuliers (ex: goroutine monopolisant un thread à cause d'un traitement synchrone).
    - les I/O sont asynchrones; quand une goroutine en fait une le runtime va associer le thread de l'OS à une autre goroutine.

    • [^] # Re: Go sans concept nouveau

      Posté par . Évalué à 6.

      l'ordonnancement des threads (goroutines) est géré par le runtime […]

      Si les goroutines étaient limitées à ça, personne n’en aurait jamais parlé. C’est ce qui se faisait déjà du temps de Java 1.1, et est facilement accessible dans la plupart des langages sous l’appellation plus courante de green thread (ou fiber sous Windows). Le fait que faire un yield appelle le scheduler est trivial.

      Ce qui est plus cool avec les goroutines c’est que c’est intégré au langage, comme en Erlang par exemple et même si Scala et d’autres font ça entièrement en bibliothèque, et qu’on peut faire des choses mois sûres mais plus puissantes (grace à la mémoire partagée). Le tout en restant dans un cadre syntaxique très proche du C et sans nouveaux concepts (parfait pour ceux qui ont la flegme d’apprendre un nouveau langage).

  • # Peut-on faire de la perf avec Rust ?

    Posté par (page perso) . Évalué à 7.

    J'aime bien ce langage car j'y retrouve des traits de OCaml, ce qui n'est pas étonnant vu que le premier compilateur de RUST a été écrit en OCaml, il a été bootstrapé depuis.

    A jeter un oeil sur la sémantique du langage, il ne devrait normalement pas y avoir de difficulté à générer du code performant avec un tel langage :

    • On a aucun pattern d'héritage très compliqué, autrement dit, seul de l'héritage statique est à l'oeuvre. A condition d'être intelligent, soit ne pas faire de table de pointeurs de fonction, on peut faire de l'objet avec de la perf.
    • Les difficultés, qui sont d'optimiser le nombre.times() sont surmontable avec un bon pattern matching.
    • Je doute qu'il ait un algorithme capable de vérifier l'exhaustivité des cas de pattern matching comme en OCaml (on a les mêmes possibilités de définition de type somme avec ses enums), mais il n'a qu'à piquer l'algo de OCaml.
    • Avec toutes les informations de typage qu'il a, il peut optimiser pas mal de fonctions.
    • Avec un bon algo d'exécution partiel, on peut avaler tous le code déjà exécutable, donc faire comme les templates C++, mais automatiquement (exemple : ya l'algo de construction d'un automate de regexp dans le code, il a la regexp dans une chaine, il lui manque la chaine sur laquelle l'appliquer, il construit l'automate à la compilation).

    Bref, je pense que Rust peut raisonnablement s'approcher du 1,2 plus lent que c++

    « Il n’y a pas de choix démocratiques contre les Traités européens » - Jean-Claude Junker

    • [^] # Re: Peut-on faire de la perf avec Rust ?

      Posté par (page perso) . Évalué à 3.

      le premier compilateur de RUST a été écrit en OCaml, il a été bootstrapé depuis.

      J'ai hésité a en parler, mais j'ai eu peur que ça fasse trop d'informations d'un coup. D'ailleurs si le code généré est encore perfectible, le code du compilateur lui-même est de l'aveu des développeurs Rust un exemple à ne pas suivre en terme de code Rust, car rempli de vieux patterns considérés comme mauvais dans les versions récente du langage.

      Bref, je pense que Rust peut raisonnablement s'approcher du 1,2 plus lent que c++

      Oui tout à fait d'accord, c'est bien pour ça que je suis aussi enthousiaste : j'espère pouvoir délaisser Python et C++ sans avoir l'impression d'avoir fait trop de compromis.

  • # 5 fois plus lent que le C ?

    Posté par . Évalué à 6.

    Je m’étonne de cette affirmation.

    D’après des benchmarks, le Rust peut être très proche des performances de code C++ :

    http://pcwalton.github.io/blog/2013/04/18/performance-of-sequential-rust-programs/
    http://attractivechaos.wordpress.com/2013/04/06/performance-of-rust-and-dart-in-sudoku-solving/

    Rust a eu la grande sagesse que n’a pas eu Go de se baser sur LLVM pour les optimisations. Cela leur permet d’avoir eu très tôt d’excellentes performances avec peu de travail sur le compilateur.

    Et sinon, je pense que pour beaucoup de développeurs C++, c’est un peu le langage attendu depuis longtemps. Aller un peu plus loin sur la sécurité (pas de pointeurs nuls, pas de mémoire partagée entre threads) tout en pouvant garder un controle très bas niveau sur la mémoire. Vivement la 1.0 :)

    • [^] # Re: 5 fois plus lent que le C ?

      Posté par (page perso) . Évalué à 2.

      J'ai vu passer plusieurs benchmarks, et si souvent il s'en sort très bien, il y a quelques cas pathologique où Rust est bien plus lent ; mais s'agit de problème localisés dans l'implémentation encore jeune. Au final, j'ai mis 5 fois plus lent comme un compromis (j'aurai pu le dire mais il était tard), histoire de ne pas tomber tout de suite dans le fanboy-isme, j'attends la 1.0 pour ça !

    • [^] # Re: 5 fois plus lent que le C ?

      Posté par . Évalué à 6.

      « pas de mémoire partagée entre threads »

      Mais c'est révolutionnaire ! Rust aurait donc inventé le concept de process !

      Bon ok, j'ai du loupé un épisode, je veux bien qu'on m'explique.

      • [^] # Re: 5 fois plus lent que le C ?

        Posté par (page perso) . Évalué à 6.

        Je ne connais pas Rust mais je pense qu'il veut parler de cette partie du journal :

        Il y a aussi une sémantique de move, qui permet par exemple à une tâche de passer la responsabilité d'une référence à une autre tâche, et donc d'être sûr à la compilation que celle-ci ne peuvent pas écrire la même zone mémoire en même temps (avec les problèmes que ça entraîne). Cela permet aussi à chaque tâche de gérer son propre pool mémoire.

        « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

        • [^] # Re: 5 fois plus lent que le C ?

          Posté par (page perso) . Évalué à 1.

          Exactement. Sachant qu'on peut quand même partager de la mémoire entre les tâches (via les ARC - Atomic Reference Counter - par exemple) ce qui est utile aussi, par exemple pour partager une grosse image dont les différentes parties vont être traitées en parallèle. Mais on va vraiment pouvoir limiter le nombre de ces objets au strict minimum.

          Ça ne remplace pas les processus systèmes : on ne peut pas faire de la séparation de privilèges, si une tâche plante toutes les autres plantent aussi du coup (ce qui est censé être rare, vu que le langage, une fois finalisé, ne devrait pas permettre les segfaults, mais ça peut arriver si on se lie à une lib C). Mais ça facilite beaucoup la programmation avec des threads, comme Go ou Erlang, chacun avec ses avantages et inconvénients.

      • [^] # Re: 5 fois plus lent que le C ?

        Posté par . Évalué à 2.

        Ce qu’il veut dire c’est que les communications entre threads se font par des canaux spécifiques d’envoi de message. C’est plus sûr que de pouvoir directement toucher à des morceaux de mémoire des autres threads, avoir besoin de faire des locks ou éventuellement les flinguer. Ça reprend donc un peu le modèle de communication inter-threads d’Erlang. Ça ne veut pas dire que chaque thread est dans un processus différent complètement cloisonné et Rust propose en fait un passe droit pour pouvoir directement toucher à la mémoire partager (on peut aussi de base accéder aux constantes des autres threads).

    • [^] # Re: 5 fois plus lent que le C ?

      Posté par . Évalué à 10.

      D’après des benchmarks, le Rust peut être très proche des performances de code C++ :
      http://pcwalton.github.io/blog/2013/04/18/performance-of-sequential-rust-programs/

      Les benchmarks pour ce genre de chose c'est généralement du flan car tu ne compares ni du code standard, ni des problématiques standards, ni une approche standard.

      Ton premier lien il désactive l'auto-vectorisation de C/C++ pour faire sa comparaison. C'est sur que si tu coupes une jambe à quelqu'un il court moins vite !

      Dans 90% des cas tu peux t'en foutre, moi la semaine dernière après avoir optimisé un bout de code tout bête en Java (je lui ai mis presque un facteur 100x) je dois me résoudre à ce qu'il soit 10x plus lent que la même chose en C++ en -O9 par ce que HotSpot n'est pas capable de vectoriser le code alors que GCC si. Si je désactive le SIMD du compilo C mon code Java est plus rapide que le code C. Quelle est la conclusion ? Java c'est lent ou c'est rapide ?

      Si tu sors de ces cas spéciaux en pratique ce qui limite les performances d'un langage c'est surtout sa conception et ses libs standards. Quand j'écris du code CPU bound dans un point chaud le code viol 95% des règles que j'applique dans tout le reste du code.
      Il n'utilise pas du tout les mêmes outils non plus.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.