Guia para Suavizar a Curva de Aprendizado de Rust
(corrode.dev)- Rust é uma linguagem que exige uma forma de pensar completamente nova, e a mentalidade influencia muito a velocidade de aprendizado
- O mais importante é ficar íntimo do compilador; mais do que apenas corrigir mensagens de erro, é essencial ter a postura de entender o motivo
- No começo, vale usar
clone()eunwrapsem medo para começar pequeno e refatorar aos poucos - É preciso escrever muito código com as próprias mãos e construir intuição e memória muscular por meio de erros e tentativas
- Rust tem uma filosofia de desenvolvimento centrada no sistema de tipos, então é necessário ler a documentação com atenção e praticar modelagem com tipos
Flattening Rust's Learning Curve
Rust é conhecida como uma linguagem difícil de aprender, mas este texto apresenta conselhos concretos, com base na experiência do autor, sobre atitude e formas de abordagem para aprender Rust com mais eficiência.
Let Your Guard Down
- Rust exige um modelo mental diferente das linguagens tradicionais
- Às vezes, iniciantes aprendem mais rápido do que veteranos → atitude e abertura são essenciais
- Em vez de ver o borrow checker como inimigo, é melhor tratá-lo como um coautor e se esforçar para entender as mensagens de erro
- É importante buscar uma compreensão profunda de por que o compilador exige parâmetros de lifetime
- Quando o código parece feio e complexo, isso pode indicar um design ruim; use isso como motivação para buscar uma abordagem melhor
- A verbosidade de Rust ajuda em aplicações grandes e favorece refatorações
- Vale a pena ativar e usar todos os lints do
clippydesde o início
Baby Steps
- No começo, não há problema em usar bastante
String,clone(),unwrape refatorar depois - Em vez de cadeias complexas de métodos, comece com
ifematchsimples - É melhor evitar async na primeira semana
- Aprenda experimentando pequenos trechos de código no Rust Playground
- Pratique com um arquivo
main.rspor conceito e escreva a maior parte do código já pensando em descartá-lo depois
Be Accurate
- Em Rust, precisão é condição de sobrevivência
- Typos e pequenos erros viram erros de compilação imediatamente, então é preciso ter cuidado
- Criar o hábito de acrescentar
&,mutautomaticamente ajuda bastante - Vídeos de streaming de desenvolvedores como
Tsodingpodem servir como boa referência
Don’t Cheat
- Depender de LLMs ou autocompletar código torna o aprendizado mais lento
- É preciso digitar com as próprias mãos e, quando algo não fizer sentido, consultar a documentação
- Evite programar no piloto automático
- Aceite os erros e use-os para entender como o compilador funciona
- Também é recomendável praticar prever se o código vai compilar antes de executá-lo
- Criar o hábito de ler e analisar o código dos outros também é importante
- Durante o aprendizado, é melhor evitar crates externos, com exceção de
serdeeanyhow
Build Good Intuitions
- Conceitos como
lifetimeeownershipficam mais fáceis com entendimento visual - Recomenda-se criar o hábito de desenhar fluxo de dados e arquitetura com ferramentas como
excalidraw - Muitos grandes engenheiros e matemáticos também usam muito bem ferramentas de visualização
Build On Top Of What You Already Know
- Em Rust, até conceitos familiares funcionam de forma diferente (por exemplo,
mut, movimentação de valores etc.) - Ainda assim, aprender por comparação com outras linguagens é útil
Exemplos:
- Trait → parecido com Interface, mas diferente
- Struct → lembra uma classe sem herança
- Closure → semelhante a lambda
- Module → namespace
- Borrow → ponteiro com dono único
- Option → Maybe monad
- Enum → algebraic data type
- Rosetta Code é útil para estudar comparando código entre várias linguagens
- Também é eficaz aprender portando código de uma linguagem familiar para Rust
- Vale a pena praticar como expressar em Rust idiomas comuns de outras linguagens, como compreensões de lista ou certos estilos de loop
Don’t Guess
- Rust é uma linguagem em que achar não basta
- Vale refletir sobre por que é necessário escrever algo como
"hello".to_string() - As mensagens de erro são extremamente úteis, então nunca ignore as pistas escondidas nelas
- Especialmente em erros relacionados ao borrow checker, é preciso seguir manualmente o fluxo de dados ao analisar
Lean on Type-Driven Development
- Rust é uma linguagem centrada no sistema de tipos
- Dá para extrair muita informação de assinaturas de função e definições de tipo
- Leia com frequência a documentação e o código-fonte da biblioteca padrão
- Projetar primeiro os tipos e depois escrever o código correspondente ajuda a criar estruturas mais corretas e reutilizáveis
- Se você expressar invariantes por meio de tipos, código incorreto nem chega a compilar
Invest Time In Finding Good Learning Resources
- Ainda não existem tantos materiais de aprendizado de Rust, então encontrar logo no início recursos que combinem com você economiza tempo
- Ferramentas de estudo como
Rustlingsdividem opiniões, dependendo do perfil de cada pessoa - Materiais mais focados em resolver problemas, como
Advent of CodeeProject Euler, podem ser mais adequados - Vídeos no YouTube devem ser usados mais para entretenimento do que como fonte principal de informação
- A forma mais eficaz é comprar um livro, ler offline e escrever o código com as próprias mãos
- Se possível, receber treinamento ou mentoria de um especialista também pode economizar muito tempo no longo prazo
Find A Coding Buddy
- Também ajuda acompanhar de perto e observar o código de colegas mais experientes
- É possível evoluir pedindo ou fazendo revisões de código em fóruns de Rust, Mastodon e outros espaços
Explain Rust Code To Non-Rust Developers
- Tentar explicar Rust para quem não conhece Rust também ajuda no aprendizado
- Também é recomendável contribuir com código abandonado de projetos open source
- Pode ser útil montar um glossário que relacione os termos de Rust com a linguagem do seu domínio de trabalho
Believe In The Long-Term Benefit
- Rust é uma linguagem voltada mais para qualidade de longo prazo do que para produtividade imediata
- É difícil virar especialista da noite para o dia, mas um mês de foco já rende muito
- Rust é uma “linguagem do Dia 2”: o primeiro dia é duro, mas o valor aumenta com o uso contínuo
- Para ter sucesso, não basta estudá-la só pelo currículo; é preciso gostar de programar de verdade
4 comentários
Antigamente, já tentei reescrever em Rust um código que eu tinha escrito em C para fins de estudo, mas tive uma experiência bem sofrida com a manipulação de ponteiros... Como eu não conseguia organizar direito na cabeça como
RceRefCellfuncionam, entre outras coisas...Depois de ler a documentação básica e o Nomicon uma vez, nessa ordem, nunca mais travei com Rust, então fico pensando se a curva de aprendizado é realmente tão íngreme assim.
Nossa, se você pegar gosto por
unwrapeclone, depois vai sofrer demais por causa de ownership...Comentários no Hacker News
Parece a leitura de “A Discipline of Programming”; o jeito moralizante do Dijkstra era realmente necessário antigamente porque muita gente nem entendia os próprios conceitos de programação. As explicações de ownership em Rust geralmente são prolixas demais; os conceitos centrais até estão lá, mas ficam escondidos embaixo dos exemplos. Em Rust, cada objeto de dados tem exatamente um dono. Essa posse pode ser transferida de modo que sempre haja apenas um dono. Se forem necessários vários donos, o verdadeiro dono deve ser uma célula de contagem de referências, e essa célula pode ser clonada. Quando o dono desaparece, o que ele possuía também desaparece. É possível pegar emprestado temporariamente o acesso a um objeto de dados usando uma referência (
ref). Posse e referência são coisas claramente diferentes. Referências podem ser passadas e armazenadas, mas não podem sobreviver por mais tempo que o objeto ao qual se referem; caso contrário, ocorre um erro de “dangling pointer”. Essas regras são rigidamente impostas em tempo de compilação pelo borrow checker. Esse é o modelo de ownership do Rust; depois que você entende isso, os detalhes acabam se reduzindo a essas regras.Talvez seja só eu, mas acho difícil acompanhar explicações de conceitos desse tipo. Foi a mesma coisa com encapsulamento: só dizem que esconde informação, sem entrar em como/por quê isso funciona concretamente. Por exemplo, em Rust eu não entendo exatamente quem é o dono. É o stack frame? Pela estrutura LIFO, fico pensando por que a posse é transferida para a função chamada, já que a stack dela desaparece antes, então não deveria haver risco. Se for por otimização, seria para liberar o objeto mais cedo? Se o dono não for o stack frame, então não sei o que ele é. Também me confunde por que só pode haver 1 referência mutável. Em ambiente single-thread, como uma função não começa antes da outra terminar, parece que não haveria problema em ambas receberem referências mutáveis. Se o problema só acontece em ambiente multithread, não daria para emitir erro apenas nesse caso? Por causa dessas dúvidas, sempre acabo desistindo quando tento estudar Rust.
Isso não é uma explicação de ownership, e sim uma explicação motivacional. A parte mais difícil e central é como ler casos em que os lifetimes ficam complicadamente entrelaçados na assinatura de uma função, e como entender e corrigir os erros do compilador que surgem ao chamar essas funções.
Fazer um resumo que pareça correto e perfeito para quem já conhece esses conceitos é muito mais fácil do que explicá-los a quem está aprendendo pela primeira vez. Se você explicar assim, alguém acostumado apenas com call-by-sharing vai entender de imediato? Acho que não.
Se alguém que não conhece Rust ler só esse resumo, não vai aprender nada sobre Rust. Vai apenas ficar com a impressão de que “essa linguagem parece ter algum tipo de magia negra no compilador”.
Os conceitos de ownership e borrowing em si são fáceis de entender. O que realmente torna Rust difícil de aprender é que a assinatura das funções e o código de uso precisam provar um ao outro que as referências não vivem mais do que os objetos. Aliás, não armazenar objetos referenciados em tipos torna essa prova menos complexa, então costuma ser uma escolha melhor.
Faz tempo que procuro textos sobre como as pessoas nos anos 60 lidavam com o estado de sistemas/aplicações no nível de assembly. Ouvi dizer que o artigo do Sketchpad, do Sutherland, tem muitos detalhes sobre estruturas de dados, mas só li os capítulos 2 e 3.
Essa explicação não faz muito sentido para mim. Ela não define claramente ownership nem borrowing; ambos parecem termos baseados em metáforas de gestão de ativos financeiros. Não sou familiarizado com Rust, mas esse vocabulário em si parece dificultar o entendimento dos conceitos. Metáforas muitas vezes são uma faca de dois gumes. Acho que ajudaria mais explicar com termos mais diretos ligados à memória.
A explicação do modelo omite completamente a distinção crucial entre empréstimos exclusivos/compartilhados (ou mutáveis/imutáveis). O Rust fez muitas escolhas sobre como permitir esses empréstimos, e isso não é intuitivo. Por exemplo, a regra de no aliasing não vem da intuição, e sim de objetivos de otimização de funções. A parte mais complicada do borrowing é que, por causa das regras de lifetime elision, as mensagens de erro do compilador às vezes apontam para um lugar totalmente diferente da causa real. Essas regras de elision não são intuitivas e foram escolhas introduzidas para simplificar.
A versão revisada do rust book pela Brown University explica muito bem o borrow checker.
A explicação parece incompleta; por exemplo, não fala sobre o que acontece quando quem pegou emprestado desaparece.
Quando diz “o verdadeiro dono deve ser uma célula de contagem de referências”, parece uma explicação feita apenas para quem já sabe o que isso significa.
Só fui entender o borrow checker depois de implementar o meu próprio.
O segundo item da segunda seção é um exagero sério. Na prática, existem inúmeros casos de código seguro que o Rust não compila. Essa complexidade surgiu para deixar claros os limites do que o compilador consegue provar.
O modelo de ownership do Rust, os lifetimes,
enume pattern matching foram muito pesados para mim no começo. Na primeira tentativa, fiquei sobrecarregado rápido demais; na segunda, tentei ler cada linha do livro e minha paciência se esgotou. Mesmo assim, percebi que Rust é uma linguagem que oferece insights mais profundos sobre programação e design de software. Só na terceira tentativa comecei de fato a aprender, reescrevendo em estilo Rust pequenos programas e scripts que eu já tinha feito antes. Nesse processo, também fui assimilando tratamento de erros idiomático de Rust, modelagem de dados com uso intenso de tipos, pattern matching etc. Depois dessa experiência, tenho convicção de que aprender Rust foi uma das melhores decisões da minha vida como programador. Definir tipos, structs eenumantecipadamente, e escrever funções com base em dados imutáveis e pattern matching, agora é algo que aplico naturalmente em outras linguagens também.Tive uma experiência parecida. Só na terceira vez aprendendo Rust comecei a pegar o jeito de verdade e consegui escrever alguns programas com eficácia. Mesmo tendo uma longa carreira em programação, às vezes é preciso repetir o aprendizado. No passado, também precisei de exatamente 3 tentativas para entender o Dagger, o framework de injeção de dependência da JVM. Talvez seja um padrão meu ao aprender coisas complexas.
É algo que aparece muito quando desenvolvedores C++ entram em contato com Rust pela primeira vez: se você pensa em termos de C++, fica brigando com o “borrow checker” o tempo todo. Quando aprende de fato as convenções do Rust, acaba levando essas convenções de volta para o C++ e escrevendo código mais robusto, mesmo sem um borrow checker lá.
O conselho de “ler o código inteiro com cuidado e corrigir os typos antes de compilar” me parece estranho. O compilador do Rust é famoso justamente por mensagens de erro muito amigáveis, então por que eu perderia tempo caçando typo manualmente? Quero que o computador pegue meus typos.
cargo fixcorrige alguns problemas automaticamente, mas não resolve tudo.Há conselhos como “não resista”, “para aprender você precisa abandonar a arrogância”, “é preciso declarar rendição”, “a resistência é inútil; se não aceitar logo, só vai prolongar o sofrimento”, “esqueça o que você sabe”; lendo isso, dá a impressão de que o SO do telescreen do Orwell foi escrito em Rust.
Rust tem uma barreira considerável para iniciantes; é muito diferente de outras linguagens — intencionalmente, mas isso também eleva a dificuldade de entrada. A sintaxe é complexa e muito concisa, a ponto de parecer que alguém digitou com o cotovelo no teclado. Um único caractere pode mudar totalmente o significado, e há muito aninhamento. Muitas funcionalidades são difíceis de entender sem base teórica, o que amplia ainda mais a complexidade; o sistema de tipos e o mecanismo de borrowing são exemplos disso. Para um usuário típico de Python ou JavaScript, é quase uma língua alienígena. Na prática, vivemos numa época em que a maioria dos programadores não tem formação de nível mestrado em ciência da computação, então Rust talvez não seja adequado. Além disso, macros deixam tudo ainda mais complicado. Se você não souber como foram definidas, é difícil entender o que o código quer dizer. Ultimamente tenho esperança de que LLMs possam reduzir essa barreira. Ainda não senti necessidade de aprender Rust, mas por causa dos LLMs talvez eu tente de novo um dia. Rust é, de fato, uma linguagem singularmente difícil de aprender.
Parece que sempre há uma opção melhor do que Rust em qualquer situação. Ainda assim, mantenho a mente aberta; talvez passe a fazer sentido quando Rust se tornar comum o bastante.
Se uma linguagem precisa de textos tentando convencer as pessoas de que vale a pena aprender com esforço, isso não sugeriria um problema no próprio design da linguagem? (Não levem muito a sério, já que eu nem aprendi Rust.)
Só consigo ler seu comentário como “se é difícil, não vale a pena”. Tudo tem prós e contras; será que o simples fato de haver desvantagens significa que não vale nem tentar? Se alguém escrevesse apaixonadamente sobre como tocar harpa é difícil, então tocar harpa seria um hobby ruim?
Quando você se torna um desenvolvedor sênior, é comum já ter esbarrado de leve nas lições que Rust valoriza, mas sem senti-las de verdade. Muita gente pensa: “já uso linguagens com garbage collection, o que Rust poderia me ensinar?”. Na prática, quando mutabilidade e referências compartilhadas se misturam, surge uma confusão enorme, e por isso muita gente passa a adotar muitos objetos imutáveis. Quando você tem objetos imutáveis, volta a pensar em como transformá-los de forma conveniente, e isso pode acabar sendo menos ergonômico do que trabalhar com objetos mutáveis. Ao tentar expressar “este objeto tem um momento em que pode ser alterado e outro em que é imutável”, você acaba chegando à necessidade de um borrow checker. Depois que ele entra em cena, fica a pergunta: “então por que ainda preciso de garbage collection?”. Muitas vezes usamos garbage collection apenas porque dá trabalho entender claramente o lifetime dos objetos. Rust faz você vivenciar diretamente esse tipo de questão fundamental.
As decisões de design do Rust muitas vezes são difíceis de entender. O Mojo também tem borrow checker, mas é muito mais fácil de aprender do que Rust por causa de algumas escolhas. A primeira é a semântica de valor. Em Rust, para iniciantes, sempre se diz para usar
clone(), mas em linguagens estáticas comuns (C, C++, Go etc.) a semântica de valor já é o padrão no dia a dia. A segunda é que, no Mojo, os lifetimes não determinam se um valor pode ser usado com base no escopo, e sim o momento em que ele deve ser destruído. Se ainda restarem referências, o lifetime é estendido; quando o uso termina, o valor é destruído imediatamente. Assim, no Mojo você não precisa lidar com erros do tipo “o valor não vive o suficiente”. Só essas duas escolhas de design já reduzem bastante a carga.Para iniciantes, qualquer linguagem é difícil de aprender, então Rust não é exatamente um caso único; programação já tem, por natureza, uma curva de aprendizado.
Acho que a existência de um texto assim diz mais sobre o autor do que sobre a linguagem. Não é uma crítica ao autor; acho legal compartilhar esse tipo de entusiasmo.
Este texto parece falar mais sobre a curva de aprendizado do Rust do que sobre quais problemas Rust resolve. Seria preciso explicar os dois lados de forma equilibrada para concluir se realmente vale a pena encarar o desafio.
Dá para discutir à vontade as escolhas de design do Rust, mas é difícil avaliar a linguagem em si apenas pelo fato de esse tipo de texto existir. Na verdade, acho que Python é a linguagem que mais precisa de textos assim. Como o número de programadores sem formação em engenharia vem aumentando, Python acabou se tornando, paradoxalmente, uma linguagem que qualquer um usa, enquanto Rust é uma linguagem que nem todo mundo consegue usar. Para algumas pessoas, Rust é muito mais fácil de aprender do que C ou Zig. Eu também adoro Python, mas reconheço que, no fundo, é uma linguagem terrível. Mesmo na era dos LLMs, pouca gente sabe que deve escrever Python otimizado; nossos amigos de IA também continuam gerando Python ineficiente se você não instruí-los explicitamente.
À pergunta “isso não seria um problema de design da linguagem?”, a resposta é “por quê?”.
Como alguém que aprendeu Rust, posso dizer que, embora eu use principalmente Python, nunca senti que Rust tivesse defeitos de linguagem nesse sentido. Pelo contrário, é uma linguagem extremamente rígida, então tentar forçá-la a ser menos “rustiana” só aumenta o sofrimento. Quando você segue o estilo do Rust, quanto mais o código cresce em complexidade, mais ajuda isso traz. Em outras linguagens, os erros costumam ser descobertos um a um em tempo de execução; em Rust, quase tudo é pego no momento da compilação. Claro que isso não impede erros lógicos, mas a integração forte com testes ajuda a lidar com isso. Há desvantagens, mas Rust definitivamente vale a pena aprender uma vez. O modo como Rust escolheu reduzir erros também pode ser aproveitado como bons hábitos de desenvolvimento em outras linguagens.
É verdade que Rust é complexo demais para que um LLM gere código Rust correto de primeira com facilidade. Ainda assim, acho isso muito melhor do que vários dos problemas de JavaScript e de outras linguagens dinâmicas ou fracamente tipadas.
Eu aprendi Rust e concordo com você. Rust é realmente complexo e tem cara de linguagem “projetada por comitê”. Tem ferramentas ótimas, e embora seja menos complexo que C++, de modo algum é uma linguagem fácil de aprender.
O problema desses textos é que eles não chegam ao essencial. Existem programas que Rust simplesmente não permite escrever. Há bons motivos para isso, mas esse fato é fundamentalmente diferente de quase todas as linguagens que a maioria das pessoas já usou. Como certamente existem programas que não podem ser escritos em Rust, é preciso aceitar isso; caso contrário, Rust não é para você.
Uma abordagem pouco comum ao aprender Rust é começar estudando apenas um subconjunto da linguagem. Por exemplo, no meu material de introdução a Rust, eu simplesmente não ensino lifetimes. Dá para escrever programas perfeitamente úteis só com funções sem lifetimes. O mesmo vale para macros: não são fáceis, mas de qualquer forma é melhor aprender um subconjunto primeiro. E, em vez de depender desde o começo só de
copy()ouclone(), acho mais correto aprender primeiro o conceito de borrowing. Borrowing é o núcleo da linguagem.A única forma de me fazer aprender Rust seria aparecerem muitas vagas pagando mais de 300 mil dólares por ano. Acho que Rust também tem potencial para substituir C++ no mercado quant no futuro. Mas já existe OCaml e, se eu tiver que aprender uma linguagem extremamente difícil e complexa, primeiro preciso ver o dinheiro. Até hoje, os empregos com melhor salário que já tive eram de Python.
Lendo estes comentários, percebi uma atitude comum em programadores antigos quando se sentem corrigidos. Quanto mais tempo a pessoa trabalha, mais fácil é ficar teimosa. Acho que cada um deveria refletir por conta própria por que passou a rejeitar as sugestões do compilador: o que exatamente quer fazer de diferente e o que está impedindo isso.