SkillsTecnológicas
Menu
Back-end

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.

Marcos RodriguesPublicado em 22 de fevereiro de 2024Atualizado em 18 de agosto de 202616 min de leitura
Desenvolvedor montando um mecanismo de software preciso que representa desempenho e segurança de memória

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.

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.

Recurso de memória central com caminhos compartilhados e acesso mutável exclusivo
O ownership define quem possui um valor; referências permitem empréstimos controlados sem transferir a posse.

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.

Pipeline de compilação e testes distribuindo software para servidor, dispositivo embarcado e módulo web
Cargo organiza compilação, testes e dependências, enquanto Rust pode atender servidores, sistemas embarcados e WebAssembly.

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: clone pode ser correto, mas primeiro pergunte quem deve possuir o valor e se uma referência resolve.
  • Usar unwrap em 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.

  1. Leia um capítulo do Rust Book e reescreva os exemplos sem copiar.
  2. Resolva exercícios curtos que provoquem erros de ownership e interprete as mensagens.
  3. Crie uma ferramenta útil para você, com argumentos, configuração e testes.
  4. Use cargo fmt, cargo clippy e cargo test desde o início.
  5. Leia código de uma crate pequena e contribua com documentação ou um teste.
  6. 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.