- O projeto tmux-rs é o trabalho de portar todo o código do tmux, escrito em C, para Rust ao longo de cerca de 6 meses
- Foi feita uma tentativa inicial de conversão automática com a ferramenta C2Rust, mas como o resultado tinha baixa manutenibilidade, a conversão acabou sendo feita manualmente
- Durante o processo de build e na interoperabilidade entre Rust e C, foram enfrentados vários bugs e problemas estruturais
- Houve questões e soluções específicas para portar para Rust padrões próprios de C, como instruções
goto, estruturas de dados baseadas em macros e parser yacc
- O projeto ainda é baseado em unsafe Rust, mas mira uma migração completa para Rust por meio de compilação e execução antecipadas
Visão geral do projeto
- O tmux-rs é um projeto que portou toda a base de código do tmux (cerca de 67.000 linhas de código C) para Rust (cerca de 81.000 linhas, excluindo comentários e linhas em branco)
- O desenvolvedor conduziu esse trabalho como um projeto pessoal, passando por muitos erros, tentativas e aprendizado no processo de migração de C para Rust
Uso do C2Rust e suas limitações
- A ideia original era usar a ferramenta de conversão automática C2Rust para portar o código C do tmux para Rust
- O código convertido automaticamente tinha baixa legibilidade, ficou mais de 3 vezes maior que o código C original e sofreu grande perda de manutenibilidade por causa de várias conversões de tipo desnecessárias, perda de nomes de constantes e outros problemas
- Durante a refatoração manual, o resultado do C2Rust acabou sendo descartado, e a abordagem mudou para portar o código diretamente para Rust consultando o código C como referência
- O fato de o C2Rust ter permitido compilar e executar já nas etapas iniciais ajudou muito a validar a viabilidade e a possibilidade real do projeto
Projeto do processo de build
- O tmux usa o sistema de build autotools e integra o código Rust e o código C existente por meio de bibliotecas estáticas
- No início, a biblioteca Rust era vinculada a um binário C, mas depois que a maior parte do código foi portada para Rust, a estrutura foi alterada para um binário Rust vinculando uma biblioteca C (usando a crate
cc)
- Para automatizar o build, foram escritos um script build.sh e o script
build.rs, permitindo verificar o build gradualmente mesmo durante a tradução
- Problemas frequentes no processo de build, como declarações de header ausentes e incompatibilidade de assinaturas de funções, foram resolvidos aos poucos em nível de função
Exemplos de bugs encontrados durante a migração
Bug 1: declaração implícita e retorno de ponteiro
- Em uma função convertida para Rust, como o tipo de retorno de ponteiro estava implicitamente declarado no código C, os 4 bytes superiores do valor de retorno eram truncados e repassados incorretamente
- A solução foi adicionar o protótipo correto da função no lado C para que o compilador passasse a agir corretamente
Bug 2: incompatibilidade de tipo em campo de struct
- Em uma struct de client, por causa de uma tradução incorreta do tipo de campo (ponteiro vs inteiro), o cálculo de offset de memória ficou desalinhado, causando erro na interpretação dos dados e segfault
- Isso foi resolvido fazendo a definição exata da struct coincidir entre C e Rust
Portando padrões próprios de C para Rust
Uso de raw pointers
- Mapear diretamente ponteiros C para referências Rust pode violar as regras de segurança do Rust, como permissividade de
null e garantia de inicialização
- Por isso, a maior parte das estruturas com ponteiros foi portada como raw pointers (
*mut, *const), sendo usada apenas em áreas inseguras
Tratamento de instruções goto
- No C2Rust, o controle de fluxo do
goto é convertido em algoritmos, mas na maioria dos casos isso pode ser implementado em Rust com blocos rotulados + break e loops rotulados + continue
Migração de estruturas de dados baseadas em macros
- O tmux implementa árvore rubro-negra intrusiva e lista encadeada em macros C
- Em Rust, foi criada uma interface semelhante usando traits genéricas e iteradores customizados (o problema de implementações duplicadas de uma única trait foi resolvido com um tipo dummy)
Conversão do parser yacc
- O tmux usa yacc (lex) para o parser de arquivo de configuração
- Em Rust, foi usada a crate lalrpop, que tem estrutura semelhante, para portar a gramática e as ações quase como estão, além da criação de um adaptador para integração com o lexer C
- Nesse processo, também foram enfrentadas limitações do lalrpop no suporte a raw pointers (como o uso de
NonNull<T>)
Ambiente de desenvolvimento e ferramentas
- O trabalho repetitivo de conversão foi feito principalmente com neovim e macros customizadas
- Ex.:
ptr == NULL → ptr.is_null() / ptr->field → (*ptr).field etc., com mapeamento manual
- Ferramentas de automação (Cursor) também foram testadas, mas como havia muito código perdido ou incorreto, a carga de revisão de código aumentou bastante
- Houve alguma ajuda para reduzir a fadiga dos dedos, mas do ponto de vista de produtividade o efeito foi limitado
Conclusão e próximos passos
- Todo o código já foi portado para Rust e a versão 0.0.1 foi publicada
- Em comparação com o C2Rust, o código manual é melhor em alguns pontos, mas ainda continua baseado em unsafe Rust e contém vários bugs
- O objetivo final é migrar para código safe Rust e concluir a portabilidade completa das funcionalidades do tmux para Rust
- O autor espera colaboração e feedback de desenvolvedores interessados em Rust e tmux por meio do GitHub Discussions
4 comentários
Ah... mas o Rust é mais leve?
Oh... parece bom, hein?
Entre os plugins do tmux, eu acabei deixando de usar o
resurectporque ele consome bastante memória e às vezes funciona de um jeito meio estranho, então fiquei curioso para saber se com o tmux-rs seria melhor.Apresentação do tmux-rs
https://rosettalens.com/s/ko/tmux-rs-intro
Comentários no Hacker News
Foi uma experiência muito marcante ler o registro deste projeto incrível, e eu queria expressar isso. Tenho grande respeito pela consistência e pela persistência do autor. A frase "é como jardinagem, mas com mais segfaults" ressoou profundamente comigo. É justamente nesses projetos sérios de hobby que mais se aprende.
A parte sobre o c2rust foi especialmente interessante. Já vi antes mudanças parecidas causadas por conversores automáticos de código entre linguagens. Essas ferramentas são muito úteis para inicializar rapidamente um projeto e provar a viabilidade, mas no fim costumam gerar um código que parece vazio e pouco natural para a linguagem de destino. Acho que a decisão de migrar, por mais doloroso que fosse, para um porte manual foi realmente a correta. Há um limite para o quanto é possível traduzir automaticamente a intenção de um código em C para um código seguro e idiomático em Rust.
Ao ler sobre o problema nº 2 de incompatibilidade no layout de
structna seção de "bugs interessantes", lembrei dos meus antigos pesadelos com interfaces de funções externas (FFI). Eu também já perdi uma semana inteira caçando uma corrupção sutil de dados causada por packing diferente destructentre C++ e C#. É o tipo de bug que te faz duvidar da própria sanidade. Encontrar algo assim exige uma paciência enorme para depurar. Meus aplausos ao autor.No geral, acho que este projeto mostra muito bem a dificuldade real e o processo de modernizar código de infraestrutura central. O próximo grande objetivo parece ser sair de
unsafepara Safe Rust, e estou realmente curioso sobre qual será a estratégia.Reescrever ponteiros brutos,
gotoe todo esse fluxo de controle complexo em um Rust idiomático e seguro sem fazer o código inteiro desmoronar pode, na prática, ser mais difícil do que o porte inicial. Fico curioso se o plano é introduzir lifetimes e o borrow checker gradualmente, módulo por módulo, e como pretendem lidar com estruturas de dados intrusivas. Imagino que substituir isso por algo comoBTreeMapda biblioteca padrão possa ter impacto de desempenho, e talvez o design intrusivo original existisse justamente por isso.De qualquer forma, é um trabalho impressionante. Obrigado por compartilhar o processo com tanto detalhe. Vou continuar acompanhando o projeto no GitHub.
Esta novidade realmente me chamou a atenção.
Há alguns anos venho desenvolvendo por conta própria o
rmuxinator, um gerenciador de sessões do tmux em Rust, algo como um clone do tmuxinator. A maior parte funciona bem, mas a vida ficou corrida e o progresso foi lento; ultimamente voltei a mexer nele mais focado em correções de bugs. Um recurso que adicionei recentemente foi permitir que ormuxinatortambém pudesse ser usado como biblioteca. Quero testar se funcionaria, na prática, fazer um fork dotmux-rs, adicionar ormuxinatorcomo dependência e iniciar sessões com arquivos de configuração por projeto. Não estou dizendo que ormuxinatordeva necessariamente ser incorporado ao upstream, mas acho que um recurso de template de sessão como esse teria sido realmente útil se já viesse embutido no próprio multiplexador de terminal.Também pensei no caminho inverso: talvez fosse ainda melhor se o
rmuxinatorusasse otmux-rscomo biblioteca e resolvesse todo o gerenciamento de sessão sem gerar comandos de shell (embora eu ainda não saiba se otmux-rssuporta isso).Quando eu terminar as correções de bugs em andamento, com certeza vou tentar pelo menos uma dessas ideias.
Enfim, reconhecimento pelo ótimo trabalho, richardscollin.
"Quando perguntam por que reescrevi o tmux em Rust, eu não tenho um bom motivo; é só um projeto de hobby. É como jardinagem, mas com mais segfaults." Eu adoro essa postura.
Nem tudo que é novo precisa ter obrigatoriamente uma grande justificativa ou utilidade prática. Projetos de hobby podem levar a descobertas inesperadas. Fiquei impressionado com o nível de detalhe do texto do autor.
Aliás, no meu jardim há segfaults de sobra. Programar um projeto novo no meu quintal parece mais seguro.
Concordo totalmente. Nem todo projeto precisa existir para mudar o mundo.
Recentemente reimplementei o
fzfem Rust: rs-fzf-cloneNão foi por um motivo especial; o
fzforiginal já funciona muito bem, e o principal objetivo era experimentar na prática os channels do Rust e algoritmos de busca fuzzy. Foi um processo de aprendizado muito divertido, e embora ofzforiginal seja melhor, isso não era necessariamente o mais importante. O objetivo era justamente tentar algo novo e experimentar."Jardinagem é a melhor desculpa para se tornar filósofo."
Quando alguém dá a entender que Rust é automaticamente superior a C, minha reação instintiva costuma ser de ceticismo. Mas eu vivo esquecendo que as pessoas fazem projetos assim simplesmente por diversão.
A frase "não precisamos necessariamente de um motivo só para criar algo novo" me marcou.
Mas, na verdade, o tmux não é algo novo.
Isso me faz pensar se também é preciso ter um motivo para reescrever um software existente em outra linguagem.
A frase "é como jardinagem, mas com mais segfaults" é engraçada. Ainda não estou familiarizado com Rust, então fico curioso sobre em que situações concretas o
unsafeé necessário.Fiquei muito impressionado com a postura deste projeto e com o fato de a maioria dos comentários estar em um tom positivo.
Sempre há quem diga que reescrever uma aplicação madura em outra linguagem é uma má ideia, mas a verdade é que se aprende muito no processo de tentar. O processo importa mesmo mais do que o resultado.
Dado o interesse que isso recebeu e a tendência de avanço da IA, acho que isso pode evoluir para um projeto de hobby muito atraente para iniciantes em Rust. Corrigir bugs simples e depois adicionar novos recursos ou fazer otimizações seria uma ótima experiência.
Como ideia, eu sugeriria um recurso em que o Gemini CLI (ou a LLM da sua preferência) funcione como um scratch buffer e interaja com diferentes janelas/painéis de uma sessão do tmux.
No meu caso, eu executo comandos em painéis sincronizados em vários servidores e gerencio manualmente falhas e afins; se a IA pudesse assumir a execução dos comandos, analisar a saída em tempo real e regenerar comandos de forma adaptativa, isso pareceria uma espécie de script de shell personalizado gerado dinamicamente.
Por exemplo, eu uso
gvimtodos os dias, mas se fosse criar um editor, não sentiria necessidade de fazê-lo igual aogvim; eu preferiria criar algo novo e criativo com apenas os recursos que eu quero. Se vou investir esse tanto de tempo, acho mais significativo tentar algo criativo e único.Acabei de portar o tmux para Fil-C em menos de uma hora, incluindo o porte do
libevente a aprovação nos testes. Funciona muito bem e a experiência é de segurança de memória total.Gosto desse tipo de projeto. Também tenho vontade de mergulhar no Rust.
Aliás, gostaria de mencionar o zellij, um multiplexador de terminal feito em Rust.
Sou apenas usuário, mas gosto de continuar procurando e migrando para soluções baseadas em Rust.
Coincidentemente eu estava vendo agora este vídeo, "Oxidise Your Command Line":
https://www.youtube.com/watch?v=rWMQ-g2QDsI
Algumas partes talvez só interessem a desenvolvedores Rust, mas há várias dicas bem úteis para qualquer pessoa acostumada com ambientes de linha de comando.
Acho que seria totalmente possível melhorar o c2rust para reduzir a perda de informação apontada pelo autor, como preservar nomes de constantes e coisas do tipo. Afinal, o custo da primeira conversão é alto.
Quando chegarmos a uma era em que grandes modelos de linguagem consigam converter automaticamente, em uma hora e com precisão, um código C inteiro para Safe Rust, este projeto talvez passe a ser visto como um exemplo muito representativo e à frente do seu tempo.
Ainda assim, o próprio autor disse que tentou isso na etapa final com o Cursor (em meados de 2025), mas que a eficiência da conversão caiu bastante, então me parece que o desempenho real ainda está longe disso.
codemod.com e outros já estão tentando isso sob o conceito de "codemods".
Codemods usam AST (árvore sintática abstrata) para permitir transformações e refatorações de código em grande escala e com rapidez.
Introdução à refatoração de APIs com codemods
A parte "grandes modelos de linguagem poderão converter perfeitamente código C complexo para Safe Rust em uma hora" soa bastante específica, e isso me chamou a atenção.
Espero que o código fique mais limpo no futuro. Já tentei usar o zellij várias vezes, mas mesmo depois de anos de desenvolvimento ele ainda não tem alguns recursos que o tmux oferece de forma muito conveniente.
Em especial, o fato de não ser possível ocultar/mostrar a status bar é o que mais me incomoda.
Veja a issue #694 de zellij-org/zellij
Os atalhos que eu uso com frequência entram em conflito com os bindings padrão do plugin de gerenciador de sessões, o que acaba bloqueando funções importantes como a seleção de diretórios.
No fim, a criação de sessões também precisa ser feita diretamente pela linha de comando, em vez de pelo plugin.