36 pontos por GN⁺ 2025-08-23 | 1 comentários | Compartilhar no WhatsApp
  • Rust é uma linguagem em que vários conceitos estão profundamente entrelaçados, e mesmo para entender programas básicos é preciso aprender muitos elementos ao mesmo tempo
  • Funções, genéricos, enums, pattern matching, traits, referências, ownership, Send/Sync, Iterator etc. são todos elementos centrais projetados para interagir entre si
  • Em comparação com JavaScript, no JS é possível escrever código conhecendo apenas alguns conceitos, mas no Rust só é possível escrever código realmente significativo ao entender o contexto da linguagem como um todo
  • Essa complexidade do Rust aumenta a barreira de aprendizado, mas ao mesmo tempo oferece segurança e consistência, além de influenciar fortemente a forma de projetar código
  • Essa composição linguística é o que torna Rust especial, e a visão de um “Rust menor” nos faz revisitar uma filosofia de linguagem cuidadosamente integrada

A dificuldade de aprender Rust

  • Apesar da alta barreira de entrada, muitas pessoas têm contribuído para melhorar documentação, APIs e diagnósticos
  • Entre os conceitos básicos estão funções como objetos de primeira classe, enums, pattern matching, genéricos, traits, referências, borrow checker, segurança de concorrência e iteradores
  • Esses conceitos dependem uns dos outros e estão entrelaçados, o que dificulta aprendê-los separadamente, e a biblioteca padrão também faz amplo uso desses recursos
  • Para entender até mesmo cerca de 20 linhas de código Rust, é preciso compreender ao mesmo tempo vários elementos, como paradigma funcional, Result e tratamento de erros, tipos genéricos, enums e iteradores

Comparação entre Rust e JavaScript

  • Ao escrever o mesmo programa de detecção de mudanças em arquivos em Rust e JS, no Rust diversos conceitos da linguagem ficam entrelaçados
  • No JS, basicamente basta entender funções e tratamento de null para escrever código funcional
  • Isso não significa simplesmente que Rust é mais difícil, mas mostra que Rust foi projetado para exigir uma compreensão estrutural da linguagem como um todo

O design interconectado do Rust

  • O núcleo do Rust é a combinação de recursos projetados de forma orgânica
    • Enums são incômodos sem pattern matching, e pattern matching também é limitado sem enums
    • Result e Iterator não podem ser implementados sem genéricos
    • Os conceitos de Send/Sync e as restrições de println só podem ser expressos com segurança graças às traits
    • O borrow checker garante a segurança de Send/Sync por meio da análise de captura de closures
  • Essa interconexão faz de Rust não apenas um conjunto de recursos, mas um sistema de linguagem integrado

A visão de um Rust menor

  • Em 2019, without.boats mencionou “Smaller Rust” ao discutir a possibilidade de um Rust menor e mais refinado
  • Hoje Rust cresceu muito mais, mas a ideia de um Rust menor nos relembra a essência de um design de linguagem cuidadosamente encaixado
  • O charme do Rust está no fato de que seus elementos linguísticos são independentes entre si e, ao mesmo tempo, quando combinados, oferecem forte expressividade e segurança

Conclusão

  • Rust é difícil de aprender, mas a consistência e integração dos conceitos entrelaçados funcionam como uma grande força
  • Graças a essa estrutura, Rust leva o desenvolvedor não apenas a escrever código, mas a adotar uma forma de pensar que considera segurança e desempenho ao mesmo tempo
  • A essência do Rust está em um “núcleo de linguagem pequeno e sofisticado”, e essa continua sendo uma filosofia importante mesmo no Rust expandido de hoje

1 comentários

 
GN⁺ 2025-08-23
Comentários do Hacker News
  • Acho irônico que até um programa JS “simples” tenha bugs. A documentação de fs.watch deixa explícito que é obrigatório verificar se filename pode ser null no callback. Em Rust, esse fato seria refletido no sistema de tipos e forçaria o tratamento, mas em JS é fácil escrever código de qualquer jeito. Documentação relacionada
    • Se usar Typescript, a checagem de null é obrigatória. Então acho que esse é um bom exemplo de como TS é uma etapa relativamente leve que aproxima JS um pouco mais da correção do lado do Rust
    • Há outro bug também: for path in paths deveria ser for (const path of paths). Em JS, sem os parênteses isso já dá erro imediatamente, mas a diferença entre in e of é que in itera sobre os índices do iterável, não sobre os valores, então na prática o índice vira string e acaba entrando como primeiro argumento de fs.watch. E nem o TypeScript necessariamente pegaria esse erro
    • Como foi apontado, a própria sintaxe do loop está incorreta, e bastaria executar para perceber na hora. Ou seja, talvez seja melhor entender que o autor não escreveu aquele código JS com muito cuidado, e que isso não tem grande relevância para o ponto principal
    • Posso não ter visto, mas fiquei curioso de onde veio kind. Em console.log("${kind} ${filename}"), o correto seria eventType (uma string), não kind
  • Quero fazer uma observação pequena. O println de Rust só consegue imprimir tipos que implementam as traits Display ou Debug. Por isso, Path não pode ser impresso diretamente. Nem todos os sistemas operacionais armazenam caminhos compatíveis com UTF-8, e todos os tipos de string de Rust são UTF-8. Ou seja, imprimir Path pode envolver perda de informação. Path retorna, via método display, um tipo que implementa Display. Rust incorporou isso ao sistema de tipos, mas em JS/TS é difícil explicitar que internamente string é UTF-16, e caminhos não Unicode exigem uso direto de TextEncoder/TextDecoder para serem tratados corretamente. Pela minha experiência antiga, quando um servidor enviava texto em Shift_JIS e eu lia com response.text(), em tempo de execução só saía uma string vazia. Se você não estiver acostumado com problemas de codificação, pode facilmente perder dias depurando esse tipo de situação. Além disso, o exemplo em JS tem bugs e erros de sintaxe que não existem no código Rust (no loop, precisa de for-of em vez de for-in). Também não dá para dizer que esse exemplo usa apenas “funções de primeira classe”; como em Rust, também é preciso entender iteradores, e ainda está usando CommonJS. Fora isso, ainda é preciso aprender async/await, Promises e top-level await, e o top-level await só recebeu suporte recentemente em alguns runtimes, incluindo Node. Ainda não é suportado por alguns engines JS, como o Hermes do React Native
    • Esse tipo de coisa é exatamente por que continuo usando Rust. O exemplo é só um caso, mas essas pequenas armadilhas e pegadinhas estão sempre espalhadas por outras linguagens. Individualmente talvez não aconteçam, mas ao longo do ciclo de vida inteiro de um programa elas se acumulam, e bugs estranhos continuam aparecendo de algum lugar, exigindo investigação constante. Em Rust isso não acontece. O sistema de tipos bloqueia antecipadamente uma quantidade absurda de casos. Na prática, depois que eu lanço um software com funcionalidades prontas em Rust, de vez em quando só adiciono recursos novos, e o esforço de corrigir bugs comuns quase desaparece. Claro, bugs lógicos podem existir em qualquer lugar, mas ele elimina na origem problemas vindos de incompatibilidades bobas de tipo/estrutura, então produtividade e manutenção acabam sendo uma experiência completamente diferente

    • Pessoalmente, sinto que não há tantos desenvolvedores em JS/TS que realmente entendam de verdade thenable/Promise e async-await. Já vi coisas assim:

      var fn = async (param) => new Promise((res, rej) => {
        fooLibraryCall(param).then(res).catch(rej);
      });
      

      Envolvem um wrapper em formato de callback dentro de uma Promise e depois usam isso de novo dentro de uma função async. Sempre me dói por dentro. De fato vejo esse tipo de código em vários lugares. Quando você ainda leva em conta import de módulos, async import(), transpile, code splitting etc., fica realmente muito complexo

  • Acho que a citação do Bjarne é, na verdade, um discurso de venda para justificar repetidamente o fato de que C++ está ficando cada vez pior. Talvez no começo fosse sincero, mas agora o modelo se repete. Vejo a estrutura assim:
    1. “Existe uma linguagem menor e mais limpa dentro do C++”
    2. Mas como não dá para extrair um subconjunto da linguagem, propõem primeiro criar um superconjunto (mais funcionalidades) e só depois fazer o subconjunto
    3. O superconjunto entra no novo C++N+1. A discussão do subconjunto “de verdade” fica para depois, e o ciclo se repete
    4. O C++N+1 fica mais complexo, e assim isso se repete para sempre Não consigo entender por que quem vê isso repetidamente continua ficando. No fim, essa “linguagem menor e mais limpa” nunca aparece. Sempre ficam presos no passo um
    • Isso me lembra o xkcd 927. A cada novo padrão o C++ fica mais complexo; há mudanças boas, mas às vezes elas não combinam bem com as versões anteriores, e o código-fonte vai ficando cada vez mais bagunçado. Administro duas bibliotecas OSS, mas hoje quase não uso mais. Ultimamente fico pensando por quanto tempo ainda vale insistir nisso. Rust foi realmente revigorante depois de vir de C++11/14/17/20. Dito isso, Rust também é bem vasto se você não conhecer o conjunto todo. O que foi apontado neste texto pareceu muito apropriado
  • Mais alguém ficou imediatamente distraído ao ver o shebang (script Rust autoexecutável)? Fiquei surpreso do mesmo jeito que quando encontrei algo parecido em Go no passado. Parece bem útil e talvez já seja suficiente para usos básicos. Também vi algo parecido em projetos que gerenciam pipelines de build/test com Rust. Para esse tipo de uso, parece ser uma alternativa muito boa. Mas eu, em geral, quando preciso de um script que vá um pouco além de bash, uso Deno+TS. Trabalho com JS há mais tempo (28 anos) e depois C# há 24 anos. Uso Node desde o início. Acho o Deno mais fácil de gerenciar que Node ou Python no aspecto de compartilhamento/centralização de pacotes. O frontmatter do cargo funciona de forma parecida
    • Fui eu quem projetou/implementou a integração de scripts no cargo (embora já tenha havido muitas implementações de terceiros ao longo do tempo). Fico muito feliz de ver um caso de uso real e de ver isso sendo mencionado. Veja também a documentação. Houve longas discussões sobre que formato seria apropriado, como isso deveria se integrar à linguagem, qual deveria ser o escopo da primeira versão etc. Neste momento estamos finalizando coisas como o guia de estilo e atualizações na referência do Rust, e o grande trabalho restante envolve detalhes do rustfmt, do rust-analyzer, correções de bugs no rustc e melhorias no relatório de erros do Cargo. Eu mesmo escrevo todos os dias scripts de reprodução de issues com cargo script
    • Na verdade eu me distraí ao começar a pesquisar pela palavra-chave da funcionalidade -Zscript. Está em andamento desde 2023, e há issues abertas que parecem bem próximas de ficar prontas. Também vi no repositório do ZomboDB que o pipeline de build é tratado com Rust, embora eu não tenha entendido completamente o contexto todo. Quero mencionar que o frontmatter do cargo é extremamente útil para portabilidade de scripts. Basta compartilhar um único arquivo, e é possível baixar e usar dependências imediatamente, sem instalação ou inicialização extra, como acontece em Python ou Node.js
    • Disseram que também dá para fazer a mesma coisa em Go; queria saber se alguém pode explicar melhor. Se for este link relacionado, também tenho interesse
    • Também usei JS e C# por muito tempo, mas em 2025 escolher algum sistema por esse tipo de motivo não me parece grande coisa. Muita coisa melhorou muito nos últimos 20 anos
    • Isso é só uma funcionalidade básica do Unix. Um arquivo que começa com #!/some/path simplesmente é executado pelo shell passando o arquivo inteiro para o comando especificado via stdin
  • Quando falam que em Rust “está surgindo uma linguagem menor e mais limpa por dentro”, fico me perguntando exatamente que linguagem seria essa. Pelo texto, parece que referências, lifetimes, traits, enums etc. continuam sendo todos necessários para funcionar, e então isso não seria tão diferente de Rust em si. Na parte final aparecem duas pistas, “o Rust que eu quero usar” e “o Rust do passado”, mas isso não me convenceu muito. Também li "Notes on a smaller Rust" do withoutboats, mas ali os objetivos de design são diferentes dos do Rust, então não é uma tentativa de virar Rust, e sim mais um conjunto de lições que Rust pode oferecer ao pensar no projeto de uma linguagem nova. Não é uma linguagem que quer ser Rust, e sim um exemplo de linguagem voltada para exigências “mainstream” (por exemplo, GC, simplificação de compilação/sintaxe etc.). Em segundo lugar, também aparece a fala de que “a linguagem pela qual me apaixonei quando a aprendi em 2018 era aquele ‘Rust menor’”, mas na prática o Rust não mudou tanto assim de forma essencial desde 2018. Mudanças de edition e afins foram majoritariamente melhorias de flexibilidade sintática, e as únicas exceções realmente grandes foram async e const. Então seria melhor dizer diretamente que “o Rust anterior à entrada de async e const era menor e mais limpo”, mas senti falta dessa explicação mais direta no texto
    • Se a ideia é um “Rust menor e mais limpo”, penso em Austral como exemplo
    • Também existe o argumento de que seria possível ter uma linguagem mais simples (menor) preservando os conceitos centrais do Rust. Por exemplo, removendo Copy trait, reborrowing, deref coercion, into_iter automático em loops, chamada automática de drop ao final do escopo (isso poderia ser chamado manualmente ou então o compilador dar erro), :Sized implícito por padrão em trait bounds, elisão de lifetimes, ergonomia de match e várias outras automações/conveniências, seria possível ter um Rust verdadeiramente mais simples em sentido mecânico. Mas uma linguagem assim seria muito desconfortável para uso cotidiano. A ironia é que esses elementos foram pensados justamente para iniciantes
    • Li isso com bastante atenção. Na prática, minha intenção realmente era dizer que Rust era menor e mais limpo antes da introdução de async e const. Não fui mais direto porque tenho muitos amigos que trabalham nessas funcionalidades. O Matklad expressou isso muito bem no lobste.rs. O Rust de 2015 era mais completo e mais consistente, mas a visão do Rust não é coerência total, e sim tornar-se uma linguagem útil para a indústria
  • Posso ter viés, mas acho que Rust é a linguagem mais próxima da perfeição. O borrow checker é chato, mas é necessário. Se fosse o mesmo código com bug em C, teria havido colapso em runtime — e no fim o bug também teria de ser corrigido. A diferença é que Rust obriga você a resolver o bug antes mesmo de compilar, enquanto em C você acaba lidando com erros no meio da madrugada. Não acho que Rust seja difícil; ela exige uma mudança de forma de pensar. É uma mudança de paradigma para escrever código seguro e protegido. Mudanças quase sempre são desconfortáveis, e acho que essa é a raiz da rejeição que muita gente tem ao Rust
    • Rust está longe de ser perfeito
      • Acho que o compilador tem liberdade demais para decidir o momento e a ordem em que Deref é aplicado. .into() e a trait From tornam conversões de tipo discretas demais. Há muitas funções de “conveniência” desse tipo também na biblioteca padrão. No fim, o tipo do objeto fica ambíguo, e conectar chamadas de função às implementações se torna difícil (embora a IDE ajude um pouco)
      • O retorno implícito (implicit return) esconde o fluxo do programa e convida a erros. Também não gosto muito do operador de interrogação
      • O Rust fragmenta demais pequenos módulos, e para fazer algo útil você acaba precisando de centenas de dependências. Cada uma precisa ser mantida e vendorizada separadamente para que a build fique estável, e isso é realmente incômodo
      • Async Rust é puro caos no momento
    • Minha insatisfação não é exatamente com o borrow checker em si, mas com o fato de que o próprio “bloco” chamado Rust ficou grande demais. Para quem gostava do Rust mais cru/incompleto de 2018 (eu incluído), o estado atual perdeu o apelo. Claro, ele é muito poderoso nas mãos de quem domina, mas eu me pergunto se realmente vale todo esse esforço. Em 2025, eu provavelmente escolheria Zig como alternativa a C/C++ (a única exceção seria trabalho com Postgres, porque o ecossistema pgrx é realmente único). Ainda assim, qualquer coisa é melhor do que trabalhar com C
  • Acho que Rust não deveria ser recomendado como primeira linguagem. Aprender a primeira linguagem já é difícil por natureza, e com Rust é complicado até conseguir ver o código rodando antes de ele ficar completamente correto por causa dos erros de compilação. Isso é frustrante demais e faz muita gente desistir. Eu aconselharia começar com Python, JavaScript ou Lua, fazer algo rápido como um jogo e iterar
    • Minha experiência foi diferente. Um engenheiro de ML na nossa empresa só conhecia Python, mas queria contribuir para a base de código em Rust; expliquei o básico por cerca de uma hora e ele se adaptou muito rápido, ficando produtivo logo em seguida. Na prática, quando você faz um jogo e ele trava porque passou uma string para uma função numérica, descobrir a causa pode consumir muito tempo. Em Rust, o compilador já aponta “aqui é string, mas deveria ser int”, então a depuração acaba sendo muito mais rápida. Em vez de perder um dia inteiro com erros de compilação, você não perde uma semana com erro em runtime
    • Eu sou a pessoa da citação no topo do blog. Já ensinei Rust como primeira linguagem para mais de 400 pessoas, então achei muito interessantes as afirmações desta thread. Com experiência direta ao longo de bastante tempo, reuni evidências não só de que isso é possível, mas de que funciona muito bem
    • Ainda não estou convencido. Gostaria de ver um bom educador tentar Rust como primeira linguagem. A geração mudou e nas universidades se usa muito Python, mas em teoria Rust pode elevar o nível do grupo como primeira linguagem (embora a taxa de reprovação possa ficar alta demais e virar problema administrativo; por outro lado, os alunos mais avançados talvez aprendam mais). Coisas como move assignment ou o significado da palavra-chave const mostram que aprender isso em Rust pode inclusive poupar o trabalho posterior de desaprender maus hábitos absorvidos em linguagens mais tradicionais
    • Em geral, eu recomendaria evitar tipagem estática como primeira linguagem. Eu gosto de tipagem estática, mas do ponto de vista do iniciante ela tende a só aumentar a confusão. Erros de compilador muitas vezes são contrafactuais, e mensagens como “o compilador não conseguiu provar que isso não é none” podem parecer bem mais difíceis do que um crash em runtime apontando o local exato num caso de teste. Se a pessoa vai imprimindo valores linha por linha e depurando manualmente, na maioria dos casos resolve rápido; mas quando fica travada em um erro obscuro do compilador, pode passar muito tempo perdida
    • Rust não é uma linguagem ruim se você conseguir absorver tudo de uma vez. O problema é que ninguém aprende linguagem assim, e sem compreender bem os conceitos principais a pessoa vai tropeçar repetidamente em Rust. E no fim ainda há muitos conceitos que ela não aprenderia em outras linguagens, então ao migrar para outra linguagem nova pode voltar a se frustrar
  • O exemplo mais próximo de “Rust simples” que já vi é Gleam. Parece bastante inspirado em Rust
    • Dizer que Gleam foi inspirado em Rust é um equívoco. O criador não afirma isso oficialmente. O compilador é feito em Rust, mas Gleam tem paradigma e runtime-alvo completamente diferentes, então não é um substituto para Rust
    • Se você quer outro estilo de “simple rust”, eu também recomendaria olhar para F#
    • A página principal do Gleam tem mensagens sobre direitos civis de pessoas negras, direitos trans e anti-nazismo, então eu não tenho interesse nenhum nessa linguagem
    • Fico curioso para saber se dá para fazer 3D com Gleam
  • O que me incomodou foi que “não explicam o que o programa Rust faz”. Há uma quantidade enorme de explicação técnica, mas nenhum resumo do que o programa realmente faz. Na prática, ele só observa mudanças em arquivos e imprime isso. O fato de até uma tarefa tão simples ficar mais complexa de implementar em Rust mostra bem a dificuldade da linguagem, porque você precisa lidar com detalhes internos que não têm relação direta com o problema real. Vejo essa complexidade como o desafio encontrado e ao mesmo tempo como uma barreira criada pela própria linguagem
    • Outras linguagens têm exatamente o mesmo problema, mas Rust ajuda a lidar com isso antes. Nem todo nome de arquivo pode ser impresso, e a maioria das linguagens simplesmente empurra esse problema para o usuário. Rust deixa erro/falha claramente explícitos no tipo de retorno, enquanto outras linguagens exigem mecanismos diferentes, como tratamento de exceções. Embora à primeira vista pareça simples, na prática o lado do Rust pode até ser mais intuitivo
    • Na verdade, a implementação é bem simples para uma linguagem de alto desempenho. Cabe inteira em uma página. Isso já não é um exemplo suficientemente simples?
    • Explicação simples nem sempre implica implementação simples. O XKCD 1425 mostra bem isso. (Ex.: verificar se uma foto foi tirada dentro de um parque nacional é fácil, mas distinguir se é foto de pássaro já exige uma equipe de pesquisa.) xkcd 1425
  • Acho que Rust é semanticamente bastante consistente e coeso. Em comparação com outras linguagens, tem menos açúcar sintático e menos truques, então parece mais intuitivo. Quase toda interface costuma seguir o padrão do módulo mem, então para entender bem a estrutura das interfaces, vale a pena começar por std::mem