Vamos tentar apresentar argumentos contra a ferrugem

Recentemente li um artigo criticando Rust. Embora houvesse muitas coisas certas sobre isso, eu não gostei - muito disso é muito controverso. No geral, não posso recomendar a leitura de nenhum artigo criticando Rust. Isso não é bom, porque é importante discutir as deficiências, e a difamação de críticas ineptas e de baixa qualidade, infelizmente, faz com que argumentos realmente bons sejam ignorados.



Então, vou tentar argumentar contra Rust.



Nem toda programação é sistemática



Rust é uma linguagem de programação de sistemas. Ele fornece controle preciso sobre a composição de dados e comportamento de execução de código em tempo de execução para máximo desempenho e flexibilidade. Ao contrário de outras linguagens de programação de sistemas, ele também fornece segurança de memória - programas com erros são encerrados de maneira bem definida, evitando comportamento indefinido (potencialmente perigoso).



Na maioria dos casos, entretanto, o desempenho absoluto ou o controle sobre os recursos de hardware não são necessários. Para essas situações, linguagens gerenciadas modernas, como Kotlin ou Go, fornecem velocidade decente, desempenho invejável e segurança de memória por meio do uso de um coletor de lixo gerenciado por memória dinamicamente.



Complexidade



O tempo do programador é caro e, no caso do Rust, você tem que gastar muito tempo aprendendo a própria linguagem. A comunidade tem trabalhado muito para criar materiais de ensino de alta qualidade, mas o idioma é muito extenso. Mesmo que seja lucrativo para você reescrever o projeto em Rust, aprender o idioma em si pode ser muito caro.



O preço do controle aprimorado é a maldição de escolha:



struct Foo     { bar: Bar         }
struct Foo<'a> { bar: &'a Bar     }
struct Foo<'a> { bar: &'a mut Bar }
struct Foo     { bar: Box<Bar>    }
struct Foo     { bar: Rc<Bar>     }
struct Foo     { bar: Arc<Bar>    }


Em Kotlin, você escreve uma aula Foo(val bar: Bar)e começa a resolver um problema. No Rust, você deve fazer escolhas, às vezes importantes, com uma sintaxe especial.



Toda essa complexidade por um motivo - não sabemos como criar uma linguagem de baixo nível mais simples e mais segura para a memória. Mas nem toda tarefa precisa de uma linguagem de baixo nível.



Veja também a apresentação Por que o C ++ se mantém flutuando quando o Vaza afundou .



Tempo de compilação



O tempo de compilação é um fator universal. Se um programa em alguma linguagem é lento para executar, mas esta linguagem permite uma compilação rápida, então o programador terá mais tempo para otimizar para acelerar o lançamento do programa!



No dilema dos genéricos, Rust escolheu deliberadamente compiladores lentos. Isso faz algum sentido (o tempo de execução está realmente acelerando), mas você terá que lutar muito por tempos de construção razoáveis ​​em projetos maiores.



rustcimplementa provavelmente o algoritmo de compilação incremental mais avançado em compiladores de produção, mas é um pouco como lutar contra o modelo de compilação embutido da linguagem.



Ao contrário do C ++, a montagem Rust não é paralelizada até o limite, o número de processos paralelos é limitado pelo comprimento do caminho crítico no gráfico de dependência. A diferença será perceptível se você tiver mais de 40 núcleos para compilar.



Em Rust, também não há análogos para o idioma pimpl , portanto, mudar a caixa requer recompilar (e não apenas vincular) todas as suas dependências inversas.



Maturidade



Cinco anos definitivamente é pouco tempo, então Rust é uma língua jovem. Embora o futuro pareça brilhante, é mais provável que em dez anos estejamos programando em C em vez de Rust (veja o efeito Lindy ). Se você escreve software há décadas, deve considerar seriamente os riscos de escolher novas tecnologias (embora escolher Java em vez de Cobol para software bancário nos anos 90 acabou sendo a escolha certa em retrospecto).



Existe apenas uma implementação completa do Rust, o compilador rustc . Implementação mrustc alternativa mais avançadapula deliberadamente muitas verificações de segurança estática. Atualmente o rustc suporta apenas um backend pronto para produção, LLVM. Consequentemente, o suporte para arquiteturas de processador é mais restrito aqui do que C, que tem uma implementação GCC e suporte para vários compiladores proprietários específicos do fornecedor.



Finalmente, Rust não tem especificações oficiais. A especificação atual está incompleta e não documenta alguns pequenos detalhes de implementação.



Alternativas



Além do Rust, existem outras linguagens para programação de sistemas, incluindo C, C ++ e Ada.



O C ++ moderno fornece ferramentas e diretrizes para melhorar a segurança. Existe até uma proposta de segurança vitalícia do objeto estilo Rust! Ao contrário do Rust, o uso dessas ferramentas não garante que não haja problemas de segurança de memória. Mas se você já oferece suporte a uma grande quantidade de código C ++, faz sentido verificar, talvez seguir as recomendações e usar sanitizantes ajude a resolver problemas de segurança. Isso é difícil, mas claramente mais fácil do que reescrever todo o código em outro idioma!



Se você estiver usando C, você pode aplicar métodos formais para provarnenhum comportamento indefinido ou apenas teste tudo completamente .



Ada é seguro para a memória, a menos que use memória dinâmica (nunca chame free).



Rust é uma linguagem interessante de custo para segurança, mas está longe de ser a única!



Kit de ferramentas



As ferramentas do Rust não são perfeitas. O kit de ferramentas básico, compilador e sistema de construção ( carga ) são freqüentemente citados como os melhores da classe.



Mas, por exemplo, algumas ferramentas relacionadas ao tempo de execução (principalmente para criação de perfil de heap) estão simplesmente ausentes - é difícil pensar sobre o tempo de execução se a ferramenta simplesmente não estiver lá! Além disso, o suporte IDE também fica muito aquém do nível de confiabilidade do Java. A refatoração complexa automatizada de um programa com milhões de linhas simplesmente não é possível no Rust.



Integração



Qualquer que seja a promessa de Rust, o mundo da programação de sistemas de hoje fala C e C ++. Rust não tenta intencionalmente imitar essas linguagens - ele não usa classes de estilo C ++ ou C ABI.



Isso significa que pontes precisam ser construídas entre os mundos. A integração não será contínua, insegura, nem sempre econômica e requer sincronização entre os idiomas. Embora a integração funcione em alguns lugares e o ferramental esteja convergindo, existem obstáculos ocasionais ao longo do caminho devido à complexidade geral.



Um problema específico é que a visão de mundo superconfiante do Cargo (ótimo para projetos Rust puros) pode dificultar a integração com sistemas de construção maiores.



atuação



"Usando LLVM" não é uma solução única para todos os problemas de desempenho. Embora eu não saiba de benchmarks comparando o desempenho de C ++ e Rust em geral, não é difícil pensar em tarefas em que Rust é inferior a C ++.



Provavelmente, o maior problema é que a semântica de movimentação de Rust é baseada memcpyem valor ( no nível do código de máquina). Por outro lado, a semântica C ++ usa referências especiais a partir das quais os dados podem ser obtidos (ponteiros no nível do código de máquina). Em teoria, o compilador deveria ver a cadeia de cópias; na prática, nem sempre é o caso: # 57077 . Um problema relacionado é a falta de alocação de novos dados - o Rust às vezes precisa copiar bytes de / para a pilha, enquanto o C ++ pode criar um objeto no local.



Curiosamente, o Rust ABI padrão (que sacrificou a estabilidade pela eficiência) às vezes tem desempenho pior do que C: # 26494 .



Finalmente, embora em teoria o código Rust deva ser mais eficiente devido às informações muito mais ricas sobre aliases, habilitar otimizações relacionadas a alias causa erros de LLVM e compilação incorreta: # 54878 .



Mas, novamente, esses são exemplos raros, às vezes a comparação é na outra direção. Por exemplo, em BoxRust sem problemas de desempenho, que têm em std::unique_ptr.



Um problema potencialmente maior é que o Rust, com suas definições genéricas, é menos expressivo que o C ++. Então, alguns truques de fórmula C ++ para alto desempenho não pode ser expresso em Rust com boa sintaxe.



Valor inseguro



Talvez a ideia seja unsafeainda mais importante para Rust do que propriedade e empréstimo. Ao separar todas as operações perigosas em blocos unsafee funções e insistir em fornecer-lhes uma interface segura de nível superior, é possível criar um sistema que simultaneamente:



  1. confiável (o unsafecódigo não verificado não pode causar comportamento indefinido),

  2. modular (diferentes blocos inseguros podem ser testados separadamente).


É bastante óbvio que este é o caso: o código Rust difundido encontra pânico, mas não estouros de buffer.



Mas as perspectivas teóricas não são tão brilhantes.



Em primeiro lugar , não existe uma definição do modelo de memória de Rust, portanto, é impossível verificar formalmente se um determinado bloco não seguro é válido ou não. Há uma definição não oficial de "coisas nas quais o rustc faz ou pode confiar" e o trabalho está em andamento em um verificador de tempo de execução , mas o modelo real não está claro. Portanto, em algum lugar pode haver algum código inseguro que funciona bem hoje, mas será declarado inválido amanhã e interromperá uma nova otimização do compilador em um ano.



Em segundo lugar, acredita-se que os blocos inseguros não são realmente modulares. Bloqueios inseguros fortes o suficiente podem, de fato, estender a linguagem. Essas duas extensões não fazem nada de errado isoladas uma da outra, mas levam a um comportamento indefinido quando usadas ao mesmo tempo: consulte Equivalência observável e Código inseguro.



Finalmente, existem erros óbvios no compilador .



Aqui estão alguns tópicos que omiti deliberadamente:



  • Economia ("mais difícil de encontrar programadores Rust") - Acho que a seção "Maturidade" reflete a essência dessa questão, que não se limita ao problema do ovo e da galinha.

  • Dependências ("stdlib é muito pequeno / muitas dependências em todos os lugares") - considerando o quão bom o Cargo e as partes relevantes da linguagem são, eu pessoalmente não vejo isso como um problema.

  • Link dinâmico ("Rust deve ter uma ABI estável") - não acho que seja um argumento forte. A monomorfização é fundamentalmente incompatível com a vinculação dinâmica e, se você realmente precisar, existe um C ABI. Eu realmente acho que as coisas podem ser melhoradas aqui, mas é improvável que estejamos falando sobre mudanças específicas no Rust .


Tópico de discussão em / r / rust .



All Articles