12 Punkte von GN⁺ 2025-07-04 | 4 Kommentare | Auf WhatsApp teilen
  • Das tmux-rs-Projekt ist das Ergebnis einer etwa sechsmonatigen Portierung der gesamten in C geschriebenen tmux-Codebasis nach Rust
  • Mithilfe des C2Rust-Tools wurde zunächst eine automatische Konvertierung versucht, doch da das Ergebnis nur schwer wartbar war, wurde die Portierung letztlich manuell durchgeführt
  • Während des Build-Prozesses und der Interoperabilität zwischen Rust und C traten mehrere Bugs und strukturelle Probleme auf
  • Es gab besondere Herausforderungen und Lösungen beim Übertragen von C-spezifischen Mustern wie goto-Anweisungen, Makro-Datenstrukturen und yacc-Parsern nach Rust
  • Das Projekt basiert derzeit noch auf unsafe Rust, zielt aber durch Pre-Compile-and-Run-Ansätze auf eine vollständige Migration nach Rust ab

Projektüberblick

  • tmux-rs ist ein Projekt, das die gesamte Codebasis von tmux (rund 67.000 Zeilen C-Code) nach Rust (rund 81.000 Zeilen ohne Kommentare und Leerzeilen) portiert
  • Der Entwickler hat diese Arbeit als Hobbyprojekt durchgeführt und dabei im Portierungsprozess von C nach Rust viel ausprobiert und gelernt

Einsatz und Grenzen von C2Rust

  • Ursprünglich sollte der tmux-C-Code mit dem automatischen Konvertierungstool C2Rust nach Rust übertragen werden
  • Der automatisch konvertierte Code war schlecht lesbar, wurde mehr als dreimal so groß wie der ursprüngliche C-Code und litt unter vielen unnötigen Typumwandlungen sowie dem Verlust von Konstantennamen, was die Wartbarkeit deutlich verschlechterte
  • Im Verlauf des manuellen Refactorings wurden die C2Rust-Ergebnisse schließlich verworfen, und der Ansatz wurde auf eine direkte Übertragung nach Rust unter Referenz auf den C-Code umgestellt
  • Dass sich bereits in der frühen Phase mit C2Rust bauen und ausführen ließ, half jedoch sehr dabei, die Machbarkeit und Sinnhaftigkeit des Projekts zu bestätigen

Entwurf des Build-Prozesses

  • tmux basiert auf dem Build-System autotools und bindet Rust-Code sowie bestehenden C-Code als statische Bibliotheken zusammen
  • Anfangs wurde eine Rust-Bibliothek an ein C-Binary gelinkt, doch nachdem der Großteil des Codes nach Rust portiert war, wurde die Struktur so geändert, dass ein Rust-Binary eine C-Bibliothek linkt (mit dem cc crate)
  • Für die Build-Automatisierung wurden ein build.sh-Skript und ein build.rs-Skript erstellt, sodass bereits während der Übersetzung schrittweise Build-Prüfungen möglich waren
  • Häufige Probleme im Build-Prozess wie fehlende Header-Deklarationen oder nicht übereinstimmende Funktionssignaturen wurden schrittweise auf Funktionsebene behoben

Beispiele für Bugs während der Portierung

Bug 1: Implizite Deklaration und Zeigerrückgabe

  • In einer nach Rust konvertierten Funktion wurde der Zeigerrückgabetyp im C-Code nur implizit deklariert, wodurch die oberen 4 Bytes des Rückgabewerts abgeschnitten und falsch übergeben wurden
  • Die Lösung bestand darin, auf der C-Seite einen korrekten Funktionsprototyp hinzuzufügen, damit der Compiler das richtige Verhalten erzeugt

Bug 2: Nicht übereinstimmender Typ eines Strukturfelds

  • Durch eine Fehlübersetzung des Feldtyps in der client-Struktur (Pointer vs. Integer) geriet die Berechnung des Speicher-Offsets durcheinander, was zu fehlerhafter Dateninterpretation und Segfaults führte
  • Das Problem wurde gelöst, indem die exakte Strukturdefinition in C und Rust angeglichen wurde

Portierung C-spezifischer Muster nach Rust

Einsatz von Raw Pointers

  • C-Pointer direkt auf Rust-Referenzen abzubilden, kann gegen Rusts Sicherheitsregeln verstoßen, etwa bei Null-Zulässigkeit oder garantierter Initialisierung
  • Deshalb wurden die meisten Pointer-Strukturen als Raw Pointer (*mut, *const) übernommen und nur in unsicheren Bereichen verwendet

Umgang mit goto-Anweisungen

  • C2Rust wandelt die Kontrollflusslogik von goto algorithmisch um, aber in den meisten Fällen lässt sich das in Rust ausreichend mit labeled block + break sowie labeled loop + continue umsetzen

Portierung makrobasierter Datenstrukturen

  • tmux implementiert intrusive Rot-Schwarz-Bäume und verkettete Listen als C-Makros
  • In Rust wurde mit generischen Traits und benutzerdefinierten Iteratoren eine ähnliche Schnittstelle umgesetzt (Probleme mit mehrfachen Implementierungen desselben Traits wurden mit einem Dummy-Typ gelöst)

Konvertierung des yacc-Parsers

  • tmux verwendet für den Parser der Konfigurationsdatei yacc(lex)
  • In Rust wurde das strukturell ähnliche lalrpop crate genutzt, um Grammatik und Aktionen direkt zu portieren; zusätzlich wurde ein Adapter für die Anbindung an den C-Lexer geschrieben
  • Dabei traten auch Grenzen bei der Raw-Pointer-Unterstützung von lalrpop auf (etwa der Einsatz von NonNull<T>)

Entwicklungsumgebung und Werkzeuge

  • Die wiederholten Konvertierungsarbeiten wurden hauptsächlich mit neovim und benutzerdefinierten Makros durchgeführt
  • Beispiele: ptr == NULLptr.is_null() / ptr->field(*ptr).field usw. wurden manuell abgebildet
  • Automatisierungswerkzeuge (Cursor) wurden ebenfalls ausprobiert, führten aber häufig zu verlorenem oder fehlerhaftem Code, wodurch der Aufwand für Code-Reviews zunahm
  • Zur Verringerung der Fingerermüdung halfen sie teilweise, in Bezug auf die Produktivität war ihr Nutzen jedoch begrenzt

Fazit und Ausblick

  • Die vollständige Portierung des Codes nach Rust ist abgeschlossen, und Version 0.0.1 wurde veröffentlicht
  • Im Vergleich zu C2Rust ist der manuell geschriebene Code in Teilen besser, basiert aber weiterhin auf unsafe Rust und enthält noch zahlreiche Bugs
  • Das endgültige Ziel ist der Übergang zu safe Rust sowie die vollständige Portierung aller tmux-Funktionen nach Rust
  • Zusammenarbeit und Feedback von Entwicklern mit Interesse an Rust oder tmux sind über GitHub Discussions willkommen

4 Kommentare

 
brainer 2025-07-04

Oh … aber ist Rust dann leichter?

 
bus710 2025-07-04

Oh … klingt gut?
Bei den tmux-Plugins gibt es mit resurrect ja auch welche, die heimlich ziemlich viel Speicher fressen und sich merkwürdig verhalten, deshalb hatte ich es weggelassen. Ich bin gespannt, ob es mit tmux-rs besser wird.

 
GN⁺ 2025-07-04
Hacker-News-Kommentare
  • Ich wollte ausdrücken, wie beeindruckt ich von diesem Projektbericht als Leseerlebnis war. Ich habe großen Respekt vor der Beständigkeit und Hartnäckigkeit des Autors. Die Formulierung „wie Gartenarbeit, nur mit mehr Segfaults“ fand ich besonders treffend. Gerade bei solchen ernsthaften Hobbyprojekten lernt man oft am meisten.
    Besonders interessant fand ich die Erfahrungen mit c2rust. Ich habe früher schon ähnliche Veränderungen durch automatische Code-Konverter zwischen Sprachen gesehen. Solche Tools sind sehr nützlich, um ein Projekt schnell anzustoßen und die Machbarkeit zu beweisen, aber am Ende kommt dabei oft Code heraus, der sich in der Zielsprache fremd und leer anfühlt. Ich denke, die Entscheidung, trotz der Schmerzen auf manuelles Portieren umzusteigen, war absolut richtig. Automatisch stößt man an Grenzen, wenn es darum geht, die Absicht von C-Code in sicheren und rust-typischen Code zu übersetzen.
    Im Abschnitt „interessante Bugs“ musste ich bei Nr. 2 über das struct-Layout-Mismatch sofort an frühere FFI-Albträume denken. Ich habe einmal eine Woche damit verbracht, subtile Datenkorruption aufzuspüren, weil das struct-Packing zwischen C++ und C# nicht übereinstimmte. Das sind semantisch völlig irrsinnige Bugs, bei denen man wirklich an seinem Verstand zweifelt. So etwas zu finden erfordert enorme Geduld beim Debuggen. Applaus an den Autor.
    Insgesamt ist dieses Projekt für mich ein gutes Beispiel dafür, wie die reale Schwierigkeit und der Prozess bei der Modernisierung von Kern-Infrastrukturcode aussehen. Das nächste große Ziel sei offenbar der Übergang von unsafe zu Safe Rust, und ich bin wirklich gespannt, welche Strategie dafür gewählt wird.
    Alle komplexen Kontrollflüsse mit Raw Pointern, goto usw. idiomatisch und sicher in Rust umzuschreiben, ohne dass dabei das ganze Programm auseinanderfällt, könnte in Wahrheit noch schwieriger sein als das erste Porting. Ich frage mich, ob geplant ist, Lifetimes und den Borrow Checker schrittweise auf Modulebene einzuführen und wie intrusive Datenstrukturen behandelt werden sollen. Wenn man so etwas durch BTreeMap aus der Standardbibliothek ersetzt, könnte das Performance-Auswirkungen haben; vielleicht war das intrusive Originaldesign ja genau deshalb so gewählt.
    Auf jeden Fall beeindruckende Arbeit. Danke, dass der Prozess so detailliert geteilt wurde. Ich werde das Projekt auf GitHub weiter verfolgen.

  • Diese Neuigkeit zieht mich gerade richtig an.
    Ich entwickle seit ein paar Jahren selbst einen auf Rust basierenden tmux-Session-Manager namens rmuxinator, so eine Art tmuxinator-Klon. Das meiste funktioniert gut, aber das Leben war beschäftigt, deshalb ging es nur langsam voran, und in letzter Zeit bin ich vor allem wieder mit Bugfixes beschäftigt. Die letzte Ergänzung war, rmuxinator auch als Bibliothek nutzbar zu machen. Ich würde gern testen, ob es funktioniert, tmux-rs zu forken, rmuxinator als Abhängigkeit einzubauen und Sessions über projektspezifische Konfigurationsdateien zu starten. Ich will nicht dafür plädieren, rmuxinator upstream aufzunehmen, aber ich denke schon, dass so eine Session-Template-Funktion sehr nützlich wäre, wenn sie direkt im Terminal-Multiplexer eingebaut wäre.
    Umgekehrt frage ich mich auch, ob es nicht besser wäre, wenn rmuxinator tmux-rs als Bibliothek verwenden würde, sodass die gesamte Session-Verwaltung ohne das Erzeugen von Shell-Kommandos auskommt — wobei ich noch nicht weiß, ob tmux-rs das überhaupt unterstützt.
    Sobald ich die aktuellen Bugfixes abgeschlossen habe, werde ich auf jeden Fall versuchen, wenigstens eine dieser Ideen umzusetzen.
    Wie auch immer: Anerkennung an richardscollin, tolle Arbeit.

  • „Auf die Frage, warum tmux in Rust neu geschrieben wurde, gibt es keinen besonders guten Grund, es ist einfach ein Hobbyprojekt. Es ist wie Gartenarbeit, nur mit mehr Segfaults.“ Diese Haltung gefällt mir sehr.
    Wenn man etwas Neues baut, braucht es nicht immer eine große Mission oder praktischen Nutzen. Gerade in Hobbyprojekten können unerwartete Entdeckungen entstehen. Der ausführliche Beitrag des Autors ist wirklich beeindruckend.
    In meinem Garten gibt es übrigens mehr als genug Segfaults. Neue Projekte zu programmieren fühlt sich in meinem Hof sicherer an.

    • Stimme völlig zu. Nicht jedes Projekt muss existieren, um die Welt zu verändern.
      Ich habe vor Kurzem fzf in Rust neu implementiert: rs-fzf-clone
      Es gab keinen besonderen Grund dafür, das bestehende fzf funktionierte bereits sehr gut; mein Hauptziel war, Rust-Channels und Fuzzy-Search-Algorithmen selbst praktisch kennenzulernen. Es war ein sehr unterhaltsamer Lernprozess, und obwohl das originale fzf besser ist, war das nicht entscheidend. Es ging darum, etwas Neues auszuprobieren und zu experimentieren.

    • „Gartenarbeit ist die beste Ausrede, um Philosoph zu werden.“

      • Ray Bradbury, Löwenzahnwein
    • Wenn jemand andeutet, Rust sei C grundsätzlich überlegen, reagiere ich reflexhaft erst einmal zynisch. Aber ich vergesse immer wieder, dass Leute solche Projekte einfach aus Spaß machen.

    • Der Satz „Wir brauchen nicht unbedingt nur einen Grund, um etwas Neues zu schaffen“ ist mir im Gedächtnis geblieben.
      Allerdings ist tmux eigentlich nichts Neues.
      Das bringt mich dazu, darüber nachzudenken, ob man überhaupt einen besonderen Grund braucht, um bestehende Software in einer anderen Sprache neu zu schreiben.

    • „Wie Gartenarbeit, nur mit mehr Segfaults“ ist ein lustiger Satz. Ich bin mit Rust noch nicht vertraut und frage mich, in welchen konkreten Situationen unsafe nötig ist.

  • Ich finde sowohl die Haltung dieses Projekts als auch den positiven Ton der meisten Kommentare sehr beeindruckend.
    Wenn man eine ausgereifte Anwendung in einer anderen Sprache neu schreibt, heißt es oft sofort, das sei keine gute Idee, aber beim tatsächlichen Versuch lernt man enorm viel. Der Prozess ist wirklich wichtiger als das Ergebnis.
    Angesichts der Aufmerksamkeit hier und der Entwicklung bei AI könnte das zu einem sehr attraktiven Hobbyprojekt für Rust-Einsteiger werden. Mit einfachen Bugs anzufangen und dann neue Funktionen oder Optimierungen beizusteuern, wäre eine große Lernerfahrung.
    Als Idee würde ich gern eine Funktion vorschlagen, bei der Gemini CLI oder ein anderes bevorzugtes LLM wie ein Scratch-Buffer mit den verschiedenen Fenstern und Panels einer tmux-Session interagiert.
    In meinem Fall führe ich Befehle auf mehreren Servern in synchronisierten Panels aus und kümmere mich manuell um Fehlschläge und Ähnliches. Wenn man die Ausführung der Kommandos einer AI überlassen und sie die Ausgabe in Echtzeit analysieren könnte, um Kommandos adaptiv neu zu erzeugen, würde sich das wie ein dynamisch generiertes, maßgeschneidertes Shell-Skript anfühlen.

    • Ich respektiere jedes Hobbyprojekt, egal was gemacht wird und auf welche Weise. Trotzdem verstehe ich nicht, warum man es interessant findet, bestehende Software einfach 1:1 von einer Sprache in eine andere zu portieren.
      Ich benutze zum Beispiel täglich gvim, aber wenn ich einen Editor bauen wollte, müsste er für mich nicht wie gvim sein; ich würde lieber kreativ etwas Neues mit genau den Funktionen bauen, die ich selbst möchte. Wenn man schon so viel Zeit investiert, scheint es mir sinnvoller, etwas kreativ Eigenständiges zu versuchen.
  • Ich habe tmux gerade in weniger als einer Stunde nach Fil-C portiert, inklusive libevent-Portierung und bestandener Tests. Läuft sehr gut und war eine Erfahrung mit vollständiger Speichersicherheit.

  • Solche Projekte gefallen mir. Ich hätte auch Lust, mich einmal richtig in Rust zu vertiefen.
    In dem Zusammenhang möchte ich zellij erwähnen, einen in Rust geschriebenen Terminal-Multiplexer.
    Ich bin nur Nutzer, aber ich schaue mich ständig nach Rust-basierten Lösungen um und migriere gern dorthin.

  • Zufällig habe ich gerade dieses Video „Oxidise Your Command Line“ gesehen.
    https://www.youtube.com/watch?v=rWMQ-g2QDsI
    Einiges davon ist vielleicht nur für Rust-Entwickler relevant, aber es gibt auch etliche nützliche Tipps für alle, die sich in der Kommandozeilen-Umgebung auskennen.

  • Ich denke, es müsste durchaus möglich sein, c2rust zu verbessern, um den vom Autor angesprochenen Informationsverlust zu verringern, etwa indem Konstantennamen erhalten bleiben. Der Aufwand des ersten Schritts ist schließlich groß.

    • Ich finde auch, dass C2Rust diese Funktion unbedingt braucht. Soweit ich es verstehe, ist der Hauptzweck dieses Tools, Basiskode zu erzeugen, der später idiomatisch nach Rust portiert werden kann. Wenn dabei aber Dinge wie Konstantendefinitionen komplett verloren gehen, ist der Produktivitätsverlust erheblich.
  • Wenn einmal die Zeit kommt, in der große Sprachmodelle gesamten C-Code in einer Stunde korrekt und automatisch in Safe Rust umwandeln können, wird dieses Projekt wie ein sehr zukunftsweisendes Paradebeispiel wirken.
    Der Autor hat am letzten Schritt wohl selbst noch Cursor ausprobiert (Stand Mitte 2025), aber weil die Effizienz der Konvertierung deutlich abfiel, scheint die praktische Leistungsfähigkeit noch weit davon entfernt zu sein.

    • codemod.com und ähnliche Projekte versuchen das bereits unter dem Konzept der „codemods“.
      Codemods nutzen ASTs (abstrakte Syntaxbäume), um schnelle, großskalige Code-Transformationen und Refactorings zu ermöglichen.
      Einführung in API-Refactoring mit Codemods

    • Der Teil „komplexen C-Code mit großen Sprachmodellen in einer Stunde perfekt in Safe Rust umwandeln“ wirkt gerade deshalb so eindrucksvoll, weil er so konkret formuliert ist.

  • Ich hoffe, dass der Code in Zukunft noch sauberer wird. Ich habe zellij mehrfach ausprobiert, aber selbst nach Jahren der Entwicklung fehlen dort noch einige Funktionen, die tmux ganz selbstverständlich bietet.
    Besonders störend finde ich, dass man die Statusleiste nicht ausblenden oder einblenden kann.
    Siehe zellij-org/zellij Issue #694

    • Für mich ist es unbrauchbar, weil man dem Session-Manager-Plugin keine Tastenkürzel zuweisen kann.
      Einige meiner häufig genutzten Keybindings kollidieren mit den Standardbelegungen des Session-Manager-Plugins, wodurch wichtige Funktionen wie die Verzeichnisauswahl blockiert werden.
      Am Ende muss ich Sessions also weiterhin direkt über die Kommandozeile erstellen statt über das Plugin.