18 pontos por xguru 2024-11-04 | 12 comentários | Compartilhar no WhatsApp

O que deu certo

  • A reescrita foi feita em etapas pequenas (incremental, stop-and-go), funcionou bem, e o novo código ficou mais fácil de ler e entender
  • Ao ganhar visibilidade sobre todo o código, surgiram oportunidades de otimização de desempenho
  • Foram removidos cerca de 1/3 a 1/2 dos códigos não utilizados. Linguagens de programação modernas como Rust ou Go encontram melhor código morto e avisam os desenvolvedores
  • Não há preocupação com acesso fora dos limites nem com overflow/underflow
  • O framework de testes embutido é muito útil
  • Fiquei feliz por poder remover os arquivos CMake

O que não deu certo

Ainda é preciso rastrear comportamento indefinido

  • Ao fazer uma reescrita incremental de C/C++ para Rust, foi necessário usar muitos ponteiros brutos e blocos unsafe{}
  • As regras do Rust também se aplicam dentro de unsafe, mas como o compilador não verifica, é fácil causar comportamento indefinido
  • Dentro de unsafe, é fácil quebrar a regra de vários ponteiros somente leitura XOR um ponteiro mutável
  • O Miri acabou sendo um salvador ao detectar isso

O Miri nem sempre funciona e ainda foi preciso usar Valgrind

  • Se você usa bibliotecas que têm partes escritas em C ou assembly, como bibliotecas criptográficas, o Miri não funciona
  • Há muito código unsafe que o Miri não verifica
  • Alguns testes precisaram ser executados com valgrind

Ainda é preciso rastrear vazamentos de memória

  • Um padrão comum em APIs C é alocar memória em MYLIB_init() e liberá-la em MYLIB_release(), mas é fácil esquecer de chamar MYLIB_release
  • Desenvolvedores Rust querem criar objetos wrapper com RAII, mas em testes que usam a API C esse recurso não pode ser usado
  • Em lógicas complexas, é difícil sempre chamar a função de limpeza. Em C isso é resolvido com goto, mas Rust não oferece suporte
  • Isso foi resolvido com o crate defer, mas o borrow checker não gosta muito

Cross-compilation nem sempre funciona

  • Assim como com o Miri, ao usar bibliotecas com partes implementadas em C ou assembly, cargo build --target=... não funciona de imediato

O Cbindgen nem sempre funciona

  • O Cbindgen é muito usado para gerar headers C a partir de um codebase Rust, mas tem limitações e bugs

ABI instável

  • Tipos úteis da biblioteca padrão como Option não têm ABI estável, então é preciso recriá-los manualmente com a anotação repr(C)

Falta de suporte a alocadores de memória customizados

  • Muitas bibliotecas C permitem que o usuário forneça um alocador em tempo de execução. Em Rust, só é possível escolher um alocador global em tempo de compilação
  • Problemas de liberação de recursos podem ser resolvidos com um alocador de arena, mas isso não é idiomático em Rust e não se integra à biblioteca padrão

Complexidade

  • É preciso usar coisas como UnsafeCell, RefCell, MaybeUninit e Pin para lidar com FFI, o que aumenta a complexidade
  • Rust puro já é complexo, e quando a camada de FFI é adicionada vira uma fera
  • Houve até desenvolvedores que recusaram trabalhar nesse codebase por causa da complexidade do Rust

Conclusão

  • No geral, houve satisfação com a reescrita em Rust, mas algumas áreas foram decepcionantes e exigiram muito mais esforço do que o esperado
  • Rust que interage muito com C parece uma linguagem completamente diferente de usar Rust puro. Há muito atrito e muitas armadilhas. Muitos dos problemas do C++ que Rust afirma resolver, na prática, não são resolvidos de forma alguma
  • Fica um profundo agradecimento aos desenvolvedores de Rust, Miri, cbindgen e afins. Eles fizeram um trabalho enorme. Ainda assim, a linguagem e as ferramentas para fazer muito C FFI são imaturas e quase passam a sensação de algo anterior à versão 1.0
  • Se a ergonomia de unsafe, a biblioteca padrão, a documentação, as ferramentas e a ABI instável melhorarem no futuro, a experiência poderá ser mais agradável
  • Microsoft e Google também parecem sentir tudo isso, e por isso estão investindo dinheiro real nessa área
  • Se você ainda não conhece Rust, é melhor que seu primeiro projeto use Rust puro e fique longe de tópicos de FFI
  • No início, foi considerado usar Zig ou Odin para essa reescrita, mas não se queria usar uma linguagem pré-1.0 em um codebase corporativo de produção. Agora fica a dúvida se a experiência realmente teria sido pior do que com Rust. Provavelmente o modelo do Rust simplesmente não combina bem com o modelo de C (ou de C++), então o atrito ao usar os dois juntos é grande demais
  • Se fosse necessário fazer um trabalho parecido no futuro, Zig seria fortemente considerado. Sempre que alguém disser "é só reescrever em Rust", mostre este texto e pergunte se a pessoa mudou de ideia

12 comentários

 
bus710 2024-11-05

Mesmo que o Zig ainda esteja em pre-v1, ele consegue usar muitas bibliotecas em C, então na prática acaba sendo mais útil do que parece. Para adicionar algo a um projeto baseado em C que já está rodando, o Zig pode ser uma escolha melhor do que Rust.

 
ahwjdekf 2024-11-04

Ao dar uma olhada em Rust, no momento em que vi a keyword unsafe, senti um arrepio...

 
jkliop890 2024-11-04

Acho que Rust não consegue resolver os problemas crônicos do C++. Isso é mais do ponto de vista prático do que sintático.

Os motivos seriam:

  1. Já existem produções demais usando C/C++. E elas estão funcionando bem e de forma estável. Além disso, na maioria dos casos, ninguém vê necessidade de portar isso para Rust.

  2. Em primeiro lugar, o hardware não é projetado partindo da premissa de contagem de referências. Muitos dos casos em que se usa C/C++ são para controlar hardware, SO, drivers e a camada binária em alta velocidade, mas, para dar suporte a Rust, o desenvolvedor de baixo nível acaba tendo que gerenciar diretamente o ciclo de vida dos recursos usando unsafe, e isso também tem um custo alto.

Acho que a experiência do autor é mais importante do que o valor potencial da linguagem ou discussões teóricas.
Na prática, tenho a impressão de que o nível de gerenciamento de recursos exigido nas áreas em que linguagens no nível de C/C++ são necessárias é algo difícil de substituir por Rust.

 
cosine20 2024-11-04

Este texto também parte de uma compreensão equivocada de Rust.
Pelo conteúdo, parece ser uma biblioteca que precisa se comunicar com frequência com o mundo externo ao Rust, e nesse ponto já é inevitável que a coisa fique mais bagunçada... Para começo de conversa, entre linguagens nativas praticamente nenhuma deixa isso “limpo”, e no caso de Rust a proposta é justamente encapsular isso com segurança no nível da linguagem; por isso, quanto mais pontos de contato surgem fora da linguagem, menos essa vantagem aparece.

Muitos dos problemas do C++ que Rust diz ter resolvido, na verdade, não foram resolvidos de forma alguma

Até certo ponto isso está correto, mas no ambiente de desenvolvimento do texto original era inevitável que esses problemas não fossem resolvidos, e acho que o problema foi abordar Rust como se fosse uma cura para tudo.

 
kotlinc 2024-11-04

Parece que entendi no sentido de que, no processo de migrar gradualmente de C/C++ para Rust, inevitavelmente é preciso usar unsafe, então mudar para Rust não teria muito sentido; em vez de fazer uma migração gradual para Rust, eu escolheria Zig. Mas em que parte do texto principal está escrito que se trata de uma biblioteca que precisa se comunicar com frequência com o ambiente fora de Rust?

 
cosine20 2024-11-04

Usar FFI significa, em si, se comunicar com fora do Rust.
E, olhando o conteúdo principal, parece que não se trata apenas de trocar algum estado ou dados simples de forma básica, mas sim de uma interação complexa entre o interno e o externo.

 
kotlinc 2024-11-05

Se você vai substituir gradualmente por Rust uma biblioteca escrita em C, usar FFI não é inevitável? Você acabaria trocando pequenas partes do programa por Rust e lidando com o restante em C via FFI; será que foi isso que você quis dizer ao descrever esse tipo de trabalho como comunicação com o exterior? Se for esse o caso, acho natural que o autor original tenha ficado cético em relação ao Rust. A menos que você reescreva todo o código de uma vez, não há os benefícios do Rust, então faz sentido recomendar Zig.

 
cosine20 2024-11-05

^-^

 
savvykang 2024-11-04

Como as partes explicitamente unsafe ficam marcadas no código-fonte, eu esperava que isso fosse útil porque permitiria identificar todo o alcance do impacto de FFI desde o ponto de entrada do programa, a menos que se use um bloco unsafe, mas parece que isso não chamou muito a atenção do autor.

 
carnoxen 2024-11-04

No momento em que você decidiu usar FFI desde o início, um projeto seguro já foi por água abaixo.

 
cosine20 2024-11-04

Isso mesmo.

 
kohs100 2024-11-04

Pois é, escreveram com toda a confiança que encheram de unsafe, e ainda assim não foi resolvido...