Linguagem Rust: Guia Prático para Começar em 2026
Aprenda linguagem de programação Rust: instalação, Cargo, ownership, borrowing, enums, tratamento de erros e primeiros projetos práticos.

A linguagem de programação Rust combina desempenho próximo ao de C e C++ com verificações rigorosas de memória e concorrência.
Isso a torna atraente para sistemas, ferramentas de linha de comando, serviços de backend, componentes WebAssembly e outras aplicações nas quais velocidade e previsibilidade importam.
O diferencial de Rust não é apenas ser rápida. Seu compilador ajuda a impedir, antes da execução, problemas como referências inválidas, uso de dados depois de liberados e acesso simultâneo inseguro.
O preço dessa proteção é uma curva inicial de aprendizado, sobretudo ao lidar com ownership, borrowing e lifetimes.
Neste guia, você instalará o ambiente oficial, criará um projeto com Cargo e entenderá os conceitos que realmente destravam a linguagem.
Os exemplos foram pensados para quem já conhece lógica de programação, mas ainda não escreveu um programa completo em Rust.
O que é a linguagem de programação Rust?
Rust é uma linguagem compilada, de propósito geral e com tipagem estática.
O código-fonte passa pelo compilador rustc, que verifica tipos, referências e várias regras de segurança antes de gerar um executável nativo.
Não há coletor de lixo executando em segundo plano; a liberação de recursos acontece de modo determinístico quando seus donos saem de escopo.
A linguagem nasceu voltada a programação de sistemas, mas hoje seu alcance é maior.
É possível escrever servidores, CLIs, bibliotecas, firmware, ferramentas de desenvolvimento e módulos para navegador.
Essa versatilidade explica por que Rust aparece ao lado de opções consolidadas em nossa seleção de linguagens de programação para aprender.
Em código seguro, o modelo de tipos evita classes inteiras de falhas de memória.
Isso não quer dizer que todo programa Rust esteja livre de bugs: regras de negócio incorretas, deadlocks, consumo excessivo de recursos e falhas externas continuam possíveis.
A promessa é mais específica e valiosa: tornar estados de memória perigosos muito mais difíceis de representar.
Quando Rust faz sentido — e quando não faz
Rust faz sentido quando o projeto precisa de baixa latência, uso eficiente de CPU e memória, controle de recursos ou alta confiança em operações concorrentes.
Também é uma boa escolha para substituir partes críticas escritas em C ou C++, criar uma ferramenta distribuída como binário único ou construir um serviço que deve manter desempenho previsível sob carga.
- Ferramentas de terminal e automação que precisam iniciar rapidamente.
- Serviços de rede, proxies, filas e componentes de infraestrutura.
- Sistemas embarcados e aplicações com recursos limitados.
- Bibliotecas nativas chamadas por Python, JavaScript ou outras linguagens.
- Módulos WebAssembly executados no navegador ou fora dele.
Por outro lado, Rust não é automaticamente a melhor escolha para um protótipo descartável, um CRUD simples com prazo muito curto ou uma equipe sem tempo para aprender o modelo de ownership.
Ecossistema, experiência do time e custo de manutenção pesam tanto quanto benchmarks. Em projetos web convencionais, vale comparar a proposta com outras linguagens para backend antes de decidir.
Como instalar Rust com rustup
O método recomendado pelo projeto é o rustup, gerenciador de toolchains.
Ele instala o compilador, o Cargo e a documentação, além de facilitar atualizações e a alternância entre versões estáveis, beta e nightly.
Siga o comando mostrado no site oficial para seu sistema operacional; no Windows, também pode ser necessário instalar as ferramentas de compilação indicadas pelo instalador.
rustc --version
cargo --version
rustup update
Os dois primeiros comandos confirmam que o ambiente está acessível pelo terminal. O terceiro atualiza a toolchain instalada.
Rust publica versões estáveis com frequência, portanto manter o ambiente atualizado reduz diferenças entre a documentação e o comportamento local.
Para trabalhar em Linux, nosso guia de distribuições Linux populares ajuda a escolher uma base adequada.
Primeiro projeto com Cargo
Cargo é o gerenciador de projetos e pacotes de Rust. Ele cria a estrutura inicial, resolve dependências, compila, executa testes e produz artefatos de release.
Em vez de chamar rustc manualmente para cada arquivo, você descreve o pacote no Cargo.toml e usa uma interface consistente.
cargo new saudacao
cd saudacao
cargo run
O comando cria Cargo.toml e src/main.rs. O arquivo principal contém uma função main, ponto de entrada do executável. Altere-o para receber um nome e gerar uma mensagem:
fn saudacao(nome: &str) -> String {
format!("Olá, {nome}!")
}
fn main() {
let nome = "Rust";
println!("{}", saudacao(nome));
}
cargo run compila e executa; cargo check apenas verifica o código e costuma ser mais rápido durante o desenvolvimento; cargo build --release ativa otimizações para distribuição.
A documentação oficial do Cargo detalha workspaces, dependências e publicação de crates.
Variáveis, mutabilidade e tipos
Variáveis são imutáveis por padrão. Essa escolha obriga o programador a declarar quando um valor realmente mudará e facilita o raciocínio sobre estado.
Use mut para permitir alteração e const para uma constante conhecida em compilação.
let tentativas = 3;
let mut pontos = 10;
pontos += 5;
const LIMITE: u32 = 100;
O compilador infere muitos tipos, mas anotações tornam intenções ambíguas explícitas.
Inteiros incluem i32, u64 e outros tamanhos; números decimais usam f32 ou f64. Tuplas agrupam valores de tipos diferentes e arrays têm tamanho fixo.
Já String possui texto alocado e modificável, enquanto &str é uma visão emprestada de uma sequência UTF-8.
Rust também permite shadowing: declarar uma nova variável com o mesmo nome.
Isso é útil para transformar um valor sem manter nomes intermediários, mas deve ser usado com clareza. Diferentemente de mut, o shadowing cria um novo vínculo e pode até mudar o tipo.
Ownership: a ideia central de Rust
Ownership, ou posse, é o modelo usado por Rust para administrar memória sem coletor de lixo.
Em termos práticos, cada valor tem um dono; só pode haver um dono por vez; e, quando o dono sai de escopo, o valor é descartado.
Tipos simples que implementam Copy, como muitos números, são copiados. Valores alocados, como String, normalmente são movidos.
let original = String::from("memória segura");
let movida = original;
println!("{movida}");
// println!("{original}"); // erro: o valor foi movido
A atribuição transfere a posse em vez de copiar silenciosamente o buffer. Isso impede que duas variáveis tentem liberar o mesmo recurso.
Se uma cópia profunda for realmente necessária, use clone e torne o custo visível: let copia = movida.clone();. A explicação completa está no capítulo oficial sobre ownership.

Borrowing, referências e slices
Transferir ownership para toda função seria inconveniente. Borrowing permite emprestar um valor por meio de referências.
Uma referência imutável, &T, permite leitura; uma referência mutável, &mut T, permite alteração.
No mesmo trecho de uso, você pode ter várias referências imutáveis ou uma referência mutável, mas não as duas formas concorrendo sobre o mesmo dado.
fn adicionar_prefixo(texto: &mut String) {
texto.insert_str(0, "Rust: ");
}
fn main() {
let mut titulo = String::from("guia prático");
adicionar_prefixo(&mut titulo);
println!("{titulo}");
}
O borrow checker acompanha por quanto tempo as referências são usadas e rejeita referências que poderiam ficar penduradas. Lifetimes representam essas relações; na maioria das funções simples, o compilador os infere.
Anotações como <'a> não prolongam a vida de um dado: apenas descrevem ao compilador a relação entre referências de entrada e saída.
Slices são referências a uma parte contígua de uma coleção. &str é um slice de texto, e &[T] representa um slice de elementos.
Receber slices em funções costuma ser mais flexível que exigir &String ou &Vec<T>, pois aceita diferentes fontes sem tomar posse.
Structs, enums e pattern matching
struct agrupa campos relacionados e modela entidades. Métodos são definidos em blocos impl. enum, por sua vez, representa um valor que pode assumir uma entre várias alternativas, e cada variante pode carregar dados diferentes.
Essa combinação permite expressar estados de domínio sem depender de valores mágicos.
enum Status {
Pendente,
Concluida { revisada: bool },
}
fn descricao(status: Status) -> &'static str {
match status {
Status::Pendente => "a fazer",
Status::Concluida { revisada: true } => "pronta e revisada",
Status::Concluida { revisada: false } => "pronta",
}
}
match exige que todas as possibilidades sejam tratadas, o que reduz estados esquecidos. Para um único padrão, if let e let else podem deixar o fluxo mais direto.
O objetivo não é usar enums em tudo, mas transformar regras importantes em tipos que o compilador consiga verificar.
Option e Result para ausência e erros
Rust não possui null como valor universal. Uma ausência esperada é representada por Option<T>, que pode ser Some(T) ou None.
Operações que podem falhar normalmente retornam Result<T, E>: Ok(T) no sucesso ou Err(E) na falha.
use std::fs;
use std::io;
fn carregar_configuracao() -> Result<String, io::Error> {
let conteudo = fs::read_to_string("config.txt")?;
Ok(conteudo)
}
O operador ? devolve o erro ao chamador e mantém o caminho de sucesso legível.
Em aplicações reais, trate o erro no nível que tem contexto para recuperar, registrar ou informar o usuário. unwrap e expect encerram o programa quando recebem uma falha; são úteis em exemplos, testes e invariantes bem justificadas, mas não substituem uma estratégia de erro.
O capítulo de tratamento de erros do Rust Book mostra quando usar falhas recuperáveis e panic!. Para APIs, esse cuidado se conecta diretamente a códigos HTTP, validação e observabilidade; veja também nosso guia de desenvolvimento de APIs.
Traits, generics e abstrações
Traits descrevem comportamentos compartilhados. São semelhantes a interfaces, embora se integrem profundamente ao sistema de tipos.
Generics permitem escrever funções e estruturas para diferentes tipos, e trait bounds declaram quais capacidades esses tipos precisam oferecer.
trait Resumo {
fn resumo(&self) -> String;
}
fn imprimir<T: Resumo>(item: &T) {
println!("{}", item.resumo());
}
Na maior parte dos casos, Rust monomorfiza código genérico: gera versões específicas na compilação e evita custo de despacho em tempo de execução.
Quando a escolha do tipo precisa acontecer dinamicamente, trait objects como Box<dyn Resumo> oferecem despacho dinâmico.
A decisão depende de flexibilidade, tamanho do binário e características de desempenho, não de uma regra absoluta.
Collections, iterators e closures
A biblioteca padrão inclui coleções como Vec<T>, String e HashMap<K, V>. Iterators percorrem dados de forma composável, enquanto closures capturam valores do ambiente.
Muitas transformações podem ser expressas sem índices manuais e sem criar coleções intermediárias desnecessárias.
let numeros = vec![1, 2, 3, 4, 5, 6];
let soma: i32 = numeros
.iter()
.filter(|n| **n % 2 == 0)
.map(|n| n * n)
.sum();
assert_eq!(soma, 56);
Iterators são preguiçosos: adaptadores como filter e map só processam elementos quando um consumidor, como sum ou collect, solicita resultados.
Preste atenção à escolha entre iter, iter_mut e into_iter: respectivamente, eles emprestam imutavelmente, emprestam mutavelmente ou consomem a coleção.
Concorrência segura
Rust leva regras de ownership para threads. Traits marcadoras como Send e Sync ajudam o compilador a verificar se um tipo pode ser transferido ou compartilhado entre threads com segurança.
Canais favorecem passagem de mensagens; Arc<T> permite ownership compartilhado atômico; e Mutex<T> protege acesso mutável.
use std::thread;
fn main() {
let dados = vec![10, 20, 30];
let tarefa = thread::spawn(move || dados.iter().sum::<i32>());
let total = tarefa.join().expect("thread falhou");
println!("{total}");
}
move transfere os dados capturados para a thread, impedindo que ela mantenha uma referência a uma variável local já destruída.
A segurança de tipos evita data races cobertas pelo modelo, mas não impede deadlocks, starvation ou lógica concorrente errada. Locks ainda devem ter escopo pequeno e uma ordem consistente.
O que unsafe realmente significa
Um bloco unsafe não desliga todas as verificações de Rust. Ele habilita um conjunto restrito de operações que o compilador não consegue provar seguras, como desreferenciar ponteiros brutos, chamar funções inseguras ou acessar determinados itens mutáveis estáticos.
As demais regras de tipos e ownership continuam ativas.
Ao usar unsafe, o programador assume a obrigação de manter invariantes que sustentam uma interface segura.
A prática recomendada é reduzir o trecho ao mínimo, documentar as condições exigidas, encapsulá-lo atrás de uma API segura e testá-lo com atenção.
O Rustonomicon, manual avançado do projeto, aprofunda esses contratos para quem realmente precisa escrever código inseguro.
Cargo, crates, testes e qualidade
Um pacote Cargo pode conter crates binários e uma crate de biblioteca. Dependências são declaradas em Cargo.toml, e Cargo.lock registra versões resolvidas para compilações reproduzíveis.
Antes de adicionar uma crate, avalie manutenção, documentação, licença, dependências transitivas e necessidade real.
cargo check
cargo test
cargo fmt --check
cargo clippy -- -D warnings
cargo build --release
Essa sequência verifica compilação, executa testes, confere formatação, aplica sugestões do linter e gera o binário otimizado.
Testes unitários podem ficar próximos do módulo sob #[cfg(test)]; testes de integração ficam no diretório tests. Em CI, execute as verificações em um ambiente limpo e trate warnings relevantes de forma consistente.
Rust 2024 é uma edição da linguagem, estabilizada com a toolchain 1.85. Edições permitem introduzir mudanças opt-in sem fragmentar o ecossistema: crates de edições diferentes podem interoperar.
Em projetos existentes, consulte o guia oficial da Rust 2024 Edition e faça a migração com testes, em vez de apenas alterar o campo edition.

Onde Rust é usado
Em backend, Rust é usado em serviços de rede e componentes que valorizam eficiência.
Frameworks assíncronos e runtimes permitem lidar com muitas conexões, mas a arquitetura continua exigindo métricas, limites, timeouts e tratamento de falhas.
Quem está começando pode primeiro dominar os fundamentos do nosso guia de backend para iniciantes e depois avaliar Rust para cargas específicas.
Em ferramentas de linha de comando, um binário nativo facilita distribuição e inicialização rápida.
Em sistemas embarcados, o controle de memória e a possibilidade de operar sem a biblioteca padrão atendem ambientes restritos.
Em WebAssembly, Rust compila módulos que podem cooperar com JavaScript; entenda o cenário no artigo sobre WebAssembly e WASI.
Também é comum introduzir Rust gradualmente: um parser, algoritmo ou biblioteca crítica pode ser exposto por FFI a uma aplicação existente.
Essa abordagem reduz risco de reescritas completas, mas exige cuidado adicional na fronteira entre linguagens, porque dados, erros e ownership precisam de contratos explícitos.
Projeto prático: analisador de palavras
Um pequeno analisador de texto reúne argumentos, arquivos, tratamento de erros, iterators e testes.
Crie o projeto com cargo new analisa-texto. O programa abaixo recebe o caminho de um arquivo, lê seu conteúdo e mostra as palavras mais frequentes.
Para manter o exemplo compacto, ele considera tokens separados por espaços e remove pontuação nas extremidades.
use std::{collections::HashMap, env, error::Error, fs};
fn frequencias(texto: &str) -> Vec<(String, usize)> {
let mut mapa = HashMap::new();
for token in texto.split_whitespace() {
let palavra = token
.trim_matches(|c: char| !c.is_alphanumeric())
.to_lowercase();
if !palavra.is_empty() {
*mapa.entry(palavra).or_insert(0) += 1;
}
}
let mut itens: Vec<_> = mapa.into_iter().collect();
itens.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
itens
}
fn executar() -> Result<(), Box<dyn Error>> {
let caminho = env::args().nth(1).ok_or("informe um arquivo")?;
let texto = fs::read_to_string(caminho)?;
for (palavra, quantidade) in frequencias(&texto).into_iter().take(10) {
println!("{palavra}: {quantidade}");
}
Ok(())
}
fn main() {
if let Err(erro) = executar() {
eprintln!("erro: {erro}");
std::process::exit(1);
}
}
Execute com cargo run -- texto.txt. O separador -- envia os argumentos seguintes ao programa, não ao Cargo. Observe como entry(...).or_insert(0) devolve uma referência mutável ao contador, e como ? propaga diferentes erros por meio de Box<dyn Error>.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conta_palavras_sem_diferenciar_maiusculas() {
let resultado = frequencias("Rust, rust seguro");
assert_eq!(resultado[0], (String::from("rust"), 2));
}
}
Depois de executar cargo test, evolua o projeto: filtre palavras muito curtas, aceite entrada padrão, exporte JSON ou percorra vários arquivos.
Cada incremento introduz uma dificuldade real sem exigir um sistema grande. Quando quiser empacotar o serviço ou sua ferramenta, veja o guia Docker descomplicado.
Erros comuns de quem está começando
- Clonar para silenciar o compilador:
clonepode ser correto, mas primeiro pergunte quem deve possuir o valor e se uma referência resolve. - Usar
unwrapem todo resultado: defina como cada falha deve ser comunicada ou recuperada. - Lutar contra o borrow checker: reduza o escopo das referências e separe leitura de alteração; mensagens do compilador geralmente apontam o conflito.
- Exagerar nos lifetimes explícitos: comece deixando a elisão trabalhar e anote apenas quando a relação entre referências for ambígua.
- Otimizar antes de medir: escreva código claro, faça benchmark no cenário real e só então escolha estruturas ou
unsafe. - Adicionar crates sem avaliação: cada dependência amplia manutenção, tempo de compilação e superfície de risco.
Uma boa técnica é ler o diagnóstico completo, não apenas a primeira linha. O compilador identifica onde o valor foi movido, onde o empréstimo começou e onde ainda está em uso.
Transforme exemplos grandes em funções pequenas para enxergar melhor essas fronteiras. Com o tempo, a estrutura que compila costuma também ser mais clara sobre responsabilidade e estado.
Plano de estudos para aprender Rust
Na primeira etapa, pratique sintaxe, tipos, funções, match, structs e enums.
Em seguida, reserve tempo específico para ownership, borrowing, slices e erros. Depois, construa uma CLI pequena com arquivos e testes.
Só então avance para async, frameworks web, FFI ou embarcados conforme seu objetivo.
- Leia um capítulo do Rust Book e reescreva os exemplos sem copiar.
- Resolva exercícios curtos que provoquem erros de ownership e interprete as mensagens.
- Crie uma ferramenta útil para você, com argumentos, configuração e testes.
- Use
cargo fmt,cargo clippyecargo testdesde o início. - Leia código de uma crate pequena e contribua com documentação ou um teste.
- Meça desempenho apenas quando houver uma carga representativa.
Evite estudar só por vídeos ou apenas por teoria. A aprendizagem acelera quando você alterna leitura, código que compila, erros intencionais e pequenos projetos.
Também não tente memorizar todos os tipos da biblioteca: aprenda a navegar pela documentação e a reconhecer os padrões Option, Result, iterators e traits.
Perguntas frequentes sobre Rust
Rust é difícil para iniciantes?
A sintaxe básica é acessível, mas ownership e borrowing exigem uma nova forma de pensar sobre memória. Quem já domina lógica de programação consegue começar; o importante é aceitar as mensagens do compilador como parte do aprendizado e trabalhar com projetos pequenos.
Rust substitui C e C++?
Rust pode substituir essas linguagens em muitos componentes novos e críticos, mas migrações dependem de bibliotecas, hardware, certificações, integração e equipe. Frequentemente, a adoção gradual de um módulo oferece uma relação melhor entre risco e benefício que uma reescrita total.
Rust tem coletor de lixo?
Não há um garbage collector obrigatório. Ownership, borrowing e destruição ao fim do escopo administram recursos. Contadores de referência, como Rc e Arc, existem quando a posse compartilhada é necessária e têm custos explícitos.
Dá para criar APIs e aplicações web?
Sim. O ecossistema possui runtimes assíncronos, frameworks HTTP, serialização e acesso a bancos de dados. Ainda assim, a escolha deve considerar maturidade das bibliotecas específicas, produtividade do time, hospedagem e requisitos de desempenho.
Qual editor usar para programar em Rust?
Qualquer editor com suporte ao Language Server Protocol pode funcionar. O rust-analyzer oferece autocompletar, navegação, diagnósticos e ações de código. Integre também rustfmt e Clippy, evitando depender apenas dos avisos visuais do editor.
Conclusão
A linguagem de programação Rust entrega controle de baixo nível com uma barreira forte contra falhas de memória.
Ownership define responsabilidades, borrowing permite reutilizar dados sem transferir posse, e tipos como Option e Result transformam ausência e falhas em caminhos explícitos.
Comece instalando rustup, crie um projeto com Cargo e pratique os conceitos em uma CLI pequena. Depois, escolha uma direção concreta — backend, ferramentas, sistemas embarcados ou WebAssembly — e aprofunde apenas o que o projeto exigir.
Rust recompensa prática deliberada: o compilador parece rigoroso no início, mas essa disciplina se converte em código mais previsível e fácil de manter.