12 points par GN⁺ 2025-07-04 | 4 commentaires | Partager sur WhatsApp
  • Le projet tmux-rs consiste à porter en Rust l’intégralité du code de tmux, écrit en C, sur environ 6 mois
  • L’auteur a d’abord tenté une conversion automatique avec l’outil C2Rust, mais le résultat était peu maintenable, ce qui l’a conduit à effectuer finalement le portage à la main
  • Il a rencontré plusieurs bugs et problèmes structurels lors du processus de build et de l’interopérabilité Rust-C
  • Il y a eu des problèmes spécifiques et des solutions pour transposer en Rust des motifs propres au C, comme les instructions goto, les structures de données via macros et les parseurs yacc
  • Le projet repose encore sur unsafe Rust, mais vise un portage complet vers Rust via une exécution préalable à la compilation

Aperçu du projet

  • tmux-rs est un projet de portage de l’ensemble de la base de code de tmux (environ 67 000 lignes de code C) vers Rust (environ 81 000 lignes, hors commentaires et lignes vides)
  • Le développeur a mené ce travail comme projet personnel, en accumulant de nombreux essais, erreurs et apprentissages durant le passage du C vers Rust

Utilisation de C2Rust et ses limites

  • À l’origine, l’objectif était de porter le code C de tmux vers Rust à l’aide de l’outil de conversion automatique C2Rust
  • Le code converti automatiquement était peu lisible, plus de trois fois plus volumineux que le code C d’origine et, à cause de multiples conversions de types inutiles, de la perte des noms de constantes, etc., sa maintenabilité s’est fortement dégradée
  • Pendant le refactoring manuel, l’auteur a fini par abandonner le résultat de C2Rust pour passer à une approche consistant à porter directement le code en Rust en s’appuyant sur le code C comme référence
  • Le fait d’avoir pu compiler et exécuter le projet dès la phase initiale grâce à C2Rust a toutefois grandement aidé à valider la pertinence et la faisabilité du projet

Conception du processus de build

  • tmux repose sur le système de build autotools, et le code Rust ainsi que le code C existant sont reliés sous forme de bibliothèque statique
  • Au départ, la bibliothèque Rust était liée à un binaire C, mais une fois l’essentiel du code porté en Rust, la structure a été inversée pour lier une bibliothèque C depuis un binaire Rust (avec la crate cc)
  • Pour automatiser la build, l’auteur a écrit un script build.sh et un script build.rs, afin de permettre une vérification progressive de la compilation tout au long de la traduction
  • Les problèmes fréquents pendant la build, comme les déclarations d’en-tête manquantes ou les incompatibilités de signatures de fonctions, ont été résolus étape par étape, fonction par fonction

Exemples de bugs rencontrés pendant le portage

Bug 1 : déclaration implicite et retour de pointeur

  • Dans une fonction convertie en Rust, le type de retour pointeur était déclaré implicitement côté C, ce qui entraînait la troncature des 4 octets de poids fort de la valeur de retour et donc une transmission incorrecte
  • La solution a consisté à ajouter côté C le prototype de fonction exact pour que le compilateur adopte le bon comportement

Bug 2 : incompatibilité de type dans un champ de structure

  • Dans la structure client, une mauvaise traduction du type d’un champ (pointeur vs entier) a perturbé le calcul des offsets mémoire, provoquant des erreurs d’interprétation des données et des segfaults
  • Le problème a été résolu en alignant exactement la définition de la structure entre le C et le Rust

Portage en Rust de motifs propres au C

Utilisation de raw pointers

  • Mapper directement des pointeurs C vers des références Rust peut enfreindre les règles de sécurité de Rust liées, par exemple, à l’acceptation de null ou aux garanties d’initialisation
  • La plupart des structures à base de pointeurs ont donc été converties en raw pointers (*mut, *const), utilisés uniquement dans des zones non sûres

Gestion des instructions goto

  • C2Rust transforme le contrôle de flux des goto en algorithme, mais dans la majorité des cas il est possible d’implémenter cela en Rust avec des blocs étiquetés + break ou des boucles étiquetées + continue

Portage de structures de données à base de macros

  • tmux implémente des arbres rouge-noir intrusifs et des listes chaînées en macros C
  • En Rust, une interface similaire a été mise en place avec des traits génériques et des itérateurs personnalisés (le problème des implémentations dupliquées pour un même trait a été résolu avec des types factices)

Conversion du parseur yacc

  • tmux utilise yacc(lex) pour le parseur du fichier de configuration
  • En Rust, l’auteur a utilisé la crate lalrpop, de structure similaire, pour porter presque à l’identique la grammaire et les actions, et a également écrit un adaptateur pour l’interopérabilité avec le lexer C
  • Ce travail a aussi mis en évidence certaines limites du support des raw pointers dans lalrpop (avec recours à NonNull<T>)

Environnement de développement et outils

  • Le travail répétitif de conversion a été réalisé principalement avec neovim et des macros personnalisées
  • Exemples : ptr == NULLptr.is_null() / ptr->field(*ptr).field
  • Des outils d’automatisation comme Cursor ont aussi été testés, mais comme ils produisaient souvent du code manquant ou erroné, la charge de revue de code augmentait fortement
  • Ils ont un peu aidé à réduire la fatigue des doigts, mais leur impact sur la productivité est resté limité

Conclusion et suite

  • L’ensemble du code a été entièrement porté vers Rust et la version 0.0.1 a été publiée
  • Par rapport à C2Rust, le code réécrit manuellement est meilleur sur certains points, mais il reste encore basé sur unsafe Rust et comporte de nombreux bugs
  • L’objectif final est de passer à du code safe Rust et d’achever un portage complet des fonctionnalités de tmux en Rust
  • L’auteur espère collaborer avec d’autres développeurs intéressés par Rust et tmux et recueillir leurs retours via GitHub Discussions

4 commentaires

 
brainer 2025-07-04

Oh… mais Rust est plus léger ?

 
bus710 2025-07-04

Oh... pas mal du tout ?
Parmi les plugins tmux, j’avais laissé de côté resurrect parce qu’il consomme pas mal de mémoire mine de rien et qu’il se comporte parfois bizarrement, donc je me demande si ce sera mieux avec tmux-rs.

 
GN⁺ 2025-07-04
Avis Hacker News
  • J’ai été touché par l’expérience de lecture de cette chronique de projet vraiment remarquable. J’aimerais exprimer mon grand respect pour la constance et la ténacité de l’auteur. La formule « c’est comme du jardinage, mais avec plus de segfaults » m’a profondément parlé. C’est souvent dans ce genre de projet hobby mené sérieusement qu’on apprend le plus.
    La partie sur c2rust m’a particulièrement intéressé. J’ai déjà vu par le passé des transformations similaires provoquées par des convertisseurs automatiques entre langages. Ces outils sont très utiles pour amorcer rapidement un projet et démontrer sa faisabilité, mais ils produisent souvent au final un code qui ne fait pas vraiment natif dans le langage cible, un code un peu vide. Je pense donc que le choix de basculer ensuite vers un portage manuel, aussi douloureux soit-il, était vraiment le bon. L’automatisation a ses limites quand il s’agit de traduire l’intention d’un code C en Rust sûr et idiomatique.
    Dans la section « bugs intéressants », le point n°2 sur le struct layout mismatch m’a rappelé mes propres cauchemars d’interfaces avec des fonctions externes (FFI). Il m’est arrivé de perdre une semaine à traquer une corruption de données subtile causée par un mauvais packing de struct entre C++ et C#. C’est le genre de bug qui vous rend fou sur le plan sémantique et vous fait vraiment douter de votre santé mentale. Il faut une énorme patience en débogage pour trouver ce genre de chose. Bravo à l’auteur.
    Dans l’ensemble, je trouve que ce projet montre très bien la difficulté réelle et le processus de modernisation d’un code d’infrastructure central. Le prochain grand objectif semble être de passer de unsafe à du Rust sûr, et je suis vraiment curieux de voir quelle stratégie sera adoptée.
    J’ai l’impression que remplacer tous les raw pointer, goto et autres flux de contrôle complexes par un Rust idiomatique et sûr, sans faire s’écrouler l’ensemble du code, peut en réalité être encore plus difficile que le portage initial. Je me demande s’il est prévu d’introduire progressivement les lifetimes et le borrow checker module par module, et comment seront gérées les structures de données intrusives. Les remplacer par quelque chose comme BTreeMap de la bibliothèque standard pourrait avoir un impact sur les performances, et je me demande si la conception intrusive d’origine n’était pas justement faite dans ce but.
    En tout cas, travail impressionnant. Merci d’avoir partagé le processus avec autant de détails. Je vais continuer à suivre le projet sur GitHub.

  • Cette nouveauté me parle directement.
    Depuis quelques années, je développe moi-même un gestionnaire de sessions tmux en Rust, rmuxinator, un peu dans l’esprit d’un clone de tmuxinator. La plupart des choses fonctionnent bien, mais la vie étant chargée, l’avancement a été lent, et ces derniers temps je m’y remets surtout pour corriger des bugs. Une fonctionnalité récente consiste à permettre aussi l’usage de rmuxinator comme bibliothèque. J’aimerais tester si forker tmux-rs, y ajouter rmuxinator comme dépendance, puis lancer des sessions à partir de fichiers de configuration par projet pourrait fonctionner en pratique. Je ne dis pas que rmuxinator doit forcément être intégré upstream, mais je me suis souvent dit que ce genre de fonctionnalité de modèles de session serait vraiment utile si elle était directement intégrée au terminal multiplexer lui-même.
    À l’inverse, je me demande aussi si ce ne serait pas encore mieux que rmuxinator utilise tmux-rs comme bibliothèque pour gérer entièrement les sessions sans générer de commandes shell, même si je ne sais pas encore si tmux-rs le permet.
    Quand j’aurai terminé les corrections de bugs en cours, je compte absolument essayer au moins une de ces idées.
    En tout cas, beau travail richardscollin.

  • « À la question de savoir pourquoi tmux a été réécrit en Rust, il n’y a pas vraiment de bonne raison, c’est juste un projet hobby. C’est comme du jardinage, mais avec plus de segfaults. » J’adore totalement cet état d’esprit.
    Quand on crée quelque chose de nouveau, il n’y a pas forcément besoin d’un grand objectif ou d’une justification purement pratique. Un projet hobby peut déboucher sur des découvertes inattendues. J’ai été impressionné par la qualité détaillée du billet de l’auteur.
    Pour information, mon jardin à moi est rempli de segfaults. Écrire un nouveau projet me paraît plus sûr que mon propre terrain.

    • Entièrement d’accord. Tous les projets n’ont pas besoin d’exister pour changer le monde.
      J’ai récemment réimplémenté fzf en Rust : rs-fzf-clone
      Je n’avais pas de raison particulière pour le faire, l’original fonctionnait déjà très bien, et mon objectif principal était surtout d’expérimenter moi-même les channels de Rust et les algorithmes de recherche floue. Ça a été un processus d’apprentissage vraiment amusant, et même si le fzf d’origine est meilleur, ce n’était pas forcément le plus important. L’objectif, c’était d’essayer et d’expérimenter quelque chose de nouveau.

    • « Le jardinage est le meilleur prétexte pour devenir philosophe. »

      • Ray Bradbury, Dandelion Wine
    • Dès que quelqu’un laisse entendre que Rust serait intrinsèquement supérieur à C, j’ai tendance à réagir avec un réflexe de cynisme. Mais j’oublie souvent que les gens font aussi ce genre de projets simplement pour le plaisir.

    • La phrase « nous n’avons pas nécessairement besoin d’une raison uniquement pour créer quelque chose de nouveau » m’a marqué.
      Cela dit, tmux n’est en réalité pas quelque chose de nouveau.
      Ça pousse à se demander s’il faut aussi une raison pour réécrire un logiciel existant dans un autre langage.

    • La formule « c’est comme du jardinage, mais avec plus de segfaults » est amusante. Je ne connais pas encore bien Rust, alors je me demande dans quels cas précis unsafe devient nécessaire.

  • Je suis très impressionné par l’attitude autour de ce projet et par le ton positif de la plupart des commentaires.
    On entend souvent dire que réécrire une application mature dans un autre langage n’est jamais une bonne idée, mais en pratique on apprend énormément rien qu’en essayant. Le processus compte vraiment plus que le résultat.
    Vu l’attention reçue ici et l’évolution actuelle de l’IA, je pense que cela pourrait devenir un projet hobby très attractif pour des débutants en Rust. Corriger d’abord des bugs simples, puis ajouter de nouvelles fonctionnalités ou optimiser certaines parties, ce serait une très bonne expérience.
    Une idée possible serait d’utiliser Gemini CLI, ou le LLM de son choix, comme une sorte de scratch buffer capable d’interagir avec différentes fenêtres/panneaux d’une session tmux.
    Dans mon cas, j’exécute des commandes sur plusieurs serveurs via des panneaux synchronisés et je gère ensuite manuellement les échecs, etc. Si une IA pouvait prendre en charge l’exécution des commandes, analyser la sortie en temps réel et régénérer les commandes de manière adaptative, on aurait un peu l’impression d’un script shell personnalisé généré dynamiquement.

    • Je respecte totalement les projets hobby, quels qu’ils soient et quelle que soit leur manière d’être menés. En revanche, je n’arrive pas à comprendre pourquoi certains trouvent intéressant de simplement porter tel quel un logiciel existant d’un langage à un autre.
      Par exemple, j’utilise gvim tous les jours, mais si je voulais créer un éditeur, je n’aurais pas besoin qu’il ressemble absolument à gvim ; j’aurais plutôt envie de créer quelque chose de nouveau, de créatif, avec seulement les fonctionnalités que je veux. À investir autant de temps, il me semble plus intéressant d’essayer quelque chose d’original et d’unique.
  • Je viens de porter tmux en Fil-C en moins d’une heure, y compris le portage de libevent et le passage des tests. Ça fonctionne très bien et j’ai obtenu une sécurité mémoire complète.

  • J’aime ce genre de projet. Ça me donne envie de me plonger moi aussi dans Rust.
    Au passage, je voudrais mentionner zellij, un terminal multiplexer écrit en Rust.
    Je ne suis qu’utilisateur, mais j’aime continuer à chercher des solutions basées sur Rust et à migrer vers elles.

  • Je venais justement de regarder cette vidéo, « Oxidise Your Command Line ».
    https://www.youtube.com/watch?v=rWMQ-g2QDsI
    Une partie ne sera peut-être utile qu’aux développeurs Rust, mais il y a aussi pas mal d’astuces utiles pour toute personne à l’aise avec l’environnement en ligne de commande.

  • Je pense qu’il y a largement matière à améliorer c2rust pour réduire la perte d’information signalée par l’auteur, notamment sur la conservation des noms de constantes. La charge du premier passage reste importante.

    • Je suis vraiment d’accord, il faut absolument cette fonctionnalité dans C2Rust. Si j’ai bien compris, l’objectif central de cet outil est de générer une base de code qui sera ensuite portée vers du Rust idiomatique. Mais si des éléments comme les définitions de constantes disparaissent complètement, la perte de productivité devient sérieuse.
  • Le jour où les grands modèles de langage pourront convertir automatiquement, correctement et en une heure l’intégralité d’un code C en Safe Rust, ce genre de projet deviendra un exemple emblématique très tourné vers l’avenir.
    Cela dit, l’auteur explique aussi avoir essayé avec Cursor à l’étape finale, et qu’à la mi-2025 l’efficacité de conversion chutait fortement, donc les performances réelles semblent encore loin du compte.

    • codemod.com et d’autres travaillent déjà sur cela sous le concept de « codemods ».
      Les codemods utilisent l’AST (arbre syntaxique abstrait) pour permettre des transformations et refactorings de code rapides et massifs.
      Présentation du refactoring d’API avec les codemods

    • La phrase « les grands modèles de langage pourront convertir parfaitement du code C complexe en Safe Rust en une heure » m’a frappé par son niveau de précision.

  • J’espère que le code va encore se nettoyer à l’avenir. J’ai essayé zellij plusieurs fois, mais même après plusieurs années de développement, il lui manque encore certaines fonctions que tmux offre très confortablement.
    En particulier, l’impossibilité de masquer/afficher la status bar est ce qui me gêne le plus.
    Voir zellij-org/zellij issue #694

    • De mon côté, je ne peux pas l’utiliser parce qu’il est impossible d’associer un raccourci au plugin de gestion des sessions.
      Une combinaison de touches que j’utilise souvent entre en conflit avec le binding par défaut du plugin de gestion des sessions, ce qui bloque des fonctions importantes comme le choix de répertoire.
      Au final, je suis obligé de créer les sessions directement en ligne de commande au lieu d’utiliser le plugin.