Índice do Roadmap
Introdução à Programação e ao Rust
O que é programação?
Programar é como ensinar um computador a executar tarefas, assim como ensinar alguém a seguir uma receita de bolo. Você fornece instruções detalhadas e o computador as executa exatamente como foram dadas.
Por que aprender Rust?
Segurança de Memória: Rust previne bugs como null pointer dereference e data races em tempo de compilação.
Performance: Velocidade comparável a C/C++ sem garbage collector.
Produtividade: Mensagens de erro claras e excelente tooling.
Mercado: Rust é a linguagem mais amada por 8 anos consecutivos no Stack Overflow.
Comparação com outras linguagens
| Característica | Rust | Python | C/C++ |
|---|---|---|---|
| Velocidade | Muito rápido | Lento | Muito rápido |
| Segurança de memória | Garantida em compilação | Garbage Collector | Manual (propenso a erros) |
| Curva de aprendizado | Moderada | Fácil | Difícil |
| Aplicações | Sistemas, Web, CLI | Scripts, ML, Web | Sistemas, Games |
Primeiros Passos
Instalando o Rust
Windows (Passo a Passo)
- Instale o Visual Studio Build Tools (necessário para compilar):
- Acesse Visual Studio Build Tools
- Baixe e execute o instalador
- Selecione "Desenvolvimento para desktop com C++"
- Clique em Instalar e aguarde
- Baixe o instalador do Rust:
- Acesse rustup.rs
- Clique em "rustup-init.exe (64-bit)"
- Execute o instalador:
- Abra o arquivo
rustup-init.exebaixado - Pressione 1 e Enter para instalação padrão
- Aguarde o download e instalação
- Abra o arquivo
- Reinicie o terminal (ou abra um novo) para carregar as variáveis de ambiente
# Verificar instalação no PowerShell ou CMD
rustc --version
cargo --version
Linux/Mac
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Verificando a instalação
rustc --version
cargo --version
Seu primeiro programa
fn main() {
println!("Olá, mundo!");
}
O println! é uma macro (note o !), não uma função. Macros em Rust podem fazer coisas que funções não podem, como aceitar número variável de argumentos. É por isso que usamos println! com exclamação!
Usando Cargo
Cargo é o gerenciador de pacotes e build system do Rust:
# Criar novo projeto
cargo new meu_projeto
cd meu_projeto
# Compilar e executar
cargo run
# Apenas compilar
cargo build
# Compilar para produção (otimizado)
cargo build --release
Fundamentos de Programação
Variáveis e Mutabilidade
Em Rust, variáveis são imutáveis por padrão. Isso ajuda a prevenir bugs.
// Variável imutável (padrão)
let idade: i32 = 30;
let nome = "Maria";
let ativo = true;
// Variável mutável - pode ser alterada
let mut contador = 0;
contador = 1; // OK!
// Constantes - sempre imutáveis, definidas em tempo de compilação
const MAX_PONTOS: u32 = 100_000;
Tipos de Dados
| Tipo | Descrição | Exemplo |
|---|---|---|
i8, i16, i32, i64, i128 | Inteiros com sinal | let x: i32 = -42; |
u8, u16, u32, u64, u128 | Inteiros sem sinal | let x: u32 = 42; |
f32, f64 | Ponto flutuante | let pi: f64 = 3.14; |
bool | Booleano | let ativo = true; |
char | Caractere Unicode | let letra = 'A'; |
String | String dinâmica | let s = String::from("olá"); |
&str | String slice | let s = "olá"; |
Operadores
// Aritméticos
let soma = 5 + 3; // 8
let sub = 10 - 4; // 6
let mult = 3 * 4; // 12
let div = 10 / 3; // 3 (divisão inteira)
let resto = 10 % 3; // 1
// Comparação
let igual = 5 == 5; // true
let diferente = 5 != 3; // true
let maior = 10 > 5; // true
let menor_igual = 5 <= 5; // true
// Lógicos
let e = true && false; // false
let ou = true || false; // true
let nao = !true; // false
Exercício: Crie variáveis para armazenar seu nome, idade e se você gosta de programação. Imprima tudo usando println!.
Controle de Fluxo
Condicionais (if/else)
let idade = 18;
// if simples
if idade >= 18 {
println!("Maior de idade");
} else {
println!("Menor de idade");
}
// if else if
let nota = 75;
if nota >= 90 {
println!("A");
} else if nota >= 70 {
println!("B");
} else {
println!("C");
}
// if como expressão (retorna valor!)
let status = if idade >= 18 { "adulto" } else { "menor" };
println!("Status: {}", status);
Match (Pattern Matching)
O match é uma das features mais poderosas do Rust:
let dia = 3;
match dia {
1 => println!("Segunda"),
2 => println!("Terça"),
3 => println!("Quarta"),
4 | 5 => println!("Quinta ou Sexta"), // múltiplos padrões
6..=7 => println!("Fim de semana"), // range inclusivo
_ => println!("Dia inválido"), // curinga (obrigatório!)
}
// match com retorno
let dia_nome = match dia {
1 => "Segunda",
2 => "Terça",
_ => "Outro dia",
};
Loops
// loop infinito (use break para sair)
let mut contador = 0;
loop {
contador += 1;
if contador == 5 {
break;
}
}
// while
let mut n = 3;
while n > 0 {
println!("{}!", n);
n -= 1;
}
println!("Lançar!");
// for com range
for i in 1..5 { // 1, 2, 3, 4 (exclusivo)
println!("{}", i);
}
for i in 1..=5 { // 1, 2, 3, 4, 5 (inclusivo)
println!("{}", i);
}
// for com iterador
let nums = [10, 20, 30];
for num in nums.iter() {
println!("{}", num);
}
Exercício: Crie um programa que verifica se um número é par ou ímpar usando if e outro usando match.
Funções
Declarando Funções
// Função simples
fn saudar() {
println!("Olá!");
}
// Função com parâmetros
fn saudar_pessoa(nome: &str) {
println!("Olá, {}!", nome);
}
// Função com retorno
fn soma(a: i32, b: i32) -> i32 {
a + b // sem ponto-e-vírgula = retorno implícito
}
// Retorno explícito
fn dobro(x: i32) -> i32 {
return x * 2; // com return, precisa de ;
}
fn main() {
saudar();
saudar_pessoa("Maria");
let resultado = soma(5, 3);
println!("Soma: {}", resultado);
}
Em Rust, a última expressão de uma função (sem ;) é retornada automaticamente. Isso é chamado de "retorno implícito" e é o estilo idiomático.
Expressões vs Statements
fn exemplo() -> i32 {
let x = 5; // statement (não retorna valor)
let y = { // bloco é uma expressão!
let temp = 3;
temp + 1 // expressão - retorna 4
}; // y = 4
x + y // expressão - retornada pela função
}
Estruturas de Dados
Arrays (tamanho fixo)
// Array com tipo inferido
let numeros = [1, 2, 3, 4, 5];
// Array com tipo explícito
let nums: [i32; 5] = [1, 2, 3, 4, 5];
// Array preenchido com mesmo valor
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// Acessando elementos
let primeiro = numeros[0];
let segundo = numeros[1];
// Iterando
for n in numeros.iter() {
println!("{}", n);
}
Vectors (tamanho dinâmico)
// Criando vectors
let mut lista = vec![10, 20, 30];
let mut vazio: Vec<i32> = Vec::new();
// Adicionando elementos
lista.push(40);
lista.push(50);
// Removendo último
let ultimo = lista.pop(); // Some(50)
// Acessando (pode causar panic!)
let segundo = lista[1];
// Acesso seguro (retorna Option)
match lista.get(10) {
Some(valor) => println!("Valor: {}", valor),
None => println!("Índice inválido"),
}
// Tamanho
println!("Tamanho: {}", lista.len());
Tuplas
// Tupla com tipos diferentes
let pessoa = ("João", 25, true);
// Acessando por índice
let nome = pessoa.0;
let idade = pessoa.1;
// Destructuring
let (nome, idade, ativo) = pessoa;
println!("{} tem {} anos", nome, idade);
HashMap
use std::collections::HashMap;
let mut agenda = HashMap::new();
// Inserindo
agenda.insert("Ana", "1234-5678");
agenda.insert("Carlos", "9876-5432");
// Acessando
if let Some(tel) = agenda.get("Ana") {
println!("Telefone: {}", tel);
}
// Iterando
for (nome, telefone) in &agenda {
println!("{}: {}", nome, telefone);
}
Ownership e Borrowing
Ownership é o conceito mais importante do Rust! É o que permite segurança de memória sem garbage collector.
As 3 Regras do Ownership
- Cada valor tem um dono (owner)
- Só pode haver um dono por vez
- Quando o dono sai de escopo, o valor é descartado
Move Semantics
let s1 = String::from("hello");
let s2 = s1; // s1 foi MOVIDO para s2
// println!("{}", s1); // ERRO! s1 não é mais válido
println!("{}", s2); // OK!
Clone (cópia profunda)
let s1 = String::from("hello");
let s2 = s1.clone(); // copia os dados
println!("s1: {}, s2: {}", s1, s2); // ambos válidos!
Borrowing (Referências)
Em vez de mover, podemos emprestar valores:
fn tamanho(s: &String) -> usize { // recebe referência
s.len()
}
fn main() {
let texto = String::from("Rust");
let tam = tamanho(&texto); // empresta com &
// texto ainda é válido!
println!("{} tem {} caracteres", texto, tam);
}
Referências Mutáveis
fn adicionar_exclamacao(s: &mut String) {
s.push('!');
}
fn main() {
let mut texto = String::from("Olá");
adicionar_exclamacao(&mut texto);
println!("{}", texto); // "Olá!"
}
Regra importante: Você pode ter MUITAS referências imutáveis OU UMA referência mutável, mas nunca ambas ao mesmo tempo!
Structs e Enums
Structs
// Definindo uma struct
struct Pessoa {
nome: String,
idade: u8,
ativo: bool,
}
// Implementando métodos
impl Pessoa {
// Construtor (função associada)
fn new(nome: String, idade: u8) -> Self {
Pessoa { nome, idade, ativo: true }
}
// Método (recebe &self)
fn apresentar(&self) {
println!("Olá, sou {} e tenho {} anos", self.nome, self.idade);
}
// Método que modifica (recebe &mut self)
fn fazer_aniversario(&mut self) {
self.idade += 1;
}
}
fn main() {
let mut pessoa = Pessoa::new(String::from("Maria"), 25);
pessoa.apresentar();
pessoa.fazer_aniversario();
println!("Agora tenho {} anos", pessoa.idade);
}
Enums
// Enum simples
enum Status {
Ativo,
Inativo,
Pendente,
}
// Enum com dados associados
enum Mensagem {
Texto(String),
Numero(i32),
Posicao { x: i32, y: i32 },
}
fn main() {
let status = Status::Ativo;
let msg = Mensagem::Texto(String::from("Olá!"));
// Pattern matching com enums
match msg {
Mensagem::Texto(s) => println!("Texto: {}", s),
Mensagem::Numero(n) => println!("Número: {}", n),
Mensagem::Posicao { x, y } => println!("Posição: ({}, {})", x, y),
}
}
Tratamento de Erros
Option - Para valores opcionais
fn encontrar_par(nums: &[i32]) -> Option<i32> {
for &n in nums {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
let nums = vec![1, 3, 5, 6, 7];
match encontrar_par(&nums) {
Some(n) => println!("Encontrado: {}", n),
None => println!("Nenhum par encontrado"),
}
// Ou usando if let
if let Some(n) = encontrar_par(&nums) {
println!("Par: {}", n);
}
// unwrap_or para valor padrão
let resultado = encontrar_par(&nums).unwrap_or(0);
}
Result - Para operações que podem falhar
fn dividir(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Divisão por zero!"))
} else {
Ok(a / b)
}
}
fn main() {
match dividir(10.0, 2.0) {
Ok(resultado) => println!("Resultado: {}", resultado),
Err(erro) => println!("Erro: {}", erro),
}
// Operador ? para propagar erros
fn calcular() -> Result<f64, String> {
let x = dividir(10.0, 2.0)?; // retorna erro se falhar
let y = dividir(x, 2.0)?;
Ok(y)
}
}
Traits e Generics
Traits (interfaces)
// Definindo um trait
trait Resumo {
fn resumir(&self) -> String;
// Método com implementação padrão
fn preview(&self) -> String {
format!("Leia mais: {}", self.resumir())
}
}
struct Artigo {
titulo: String,
conteudo: String,
}
// Implementando o trait
impl Resumo for Artigo {
fn resumir(&self) -> String {
format!("{}: {}", self.titulo, &self.conteudo[..50])
}
}
Generics
// Função genérica
fn maior<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
// Struct genérica
struct Ponto<T> {
x: T,
y: T,
}
impl<T> Ponto<T> {
fn new(x: T, y: T) -> Self {
Ponto { x, y }
}
}
fn main() {
let p1 = Ponto::new(5, 10);
let p2 = Ponto::new(1.0, 2.5);
println!("Maior: {}", maior(5, 10));
}
Arquivos e I/O
use std::fs::File;
use std::io::{Read, Write};
fn main() -> std::io::Result<()> {
// Escrevendo
let mut arquivo = File::create("dados.txt")?;
arquivo.write_all(b"Olá, Rust!")?;
// Lendo
let mut conteudo = String::new();
let mut arquivo = File::open("dados.txt")?;
arquivo.read_to_string(&mut conteudo)?;
println!("{}", conteudo);
Ok(())
}
Concorrência
use std::thread;
use std::sync::mpsc;
fn main() {
// Criando thread
let handle = thread::spawn(|| {
println!("Thread executando!");
});
handle.join().unwrap();
// Comunicação entre threads (channels)
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("Mensagem da thread").unwrap();
});
let msg = rx.recv().unwrap();
println!("Recebido: {}", msg);
}
Testes
fn soma(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_soma_positivos() {
assert_eq!(soma(2, 2), 4);
}
#[test]
fn test_soma_negativos() {
assert_eq!(soma(-1, -1), -2);
}
#[test]
#[should_panic]
fn test_que_deve_falhar() {
panic!("Este teste deve falhar!");
}
}
// Rodar testes: cargo test
Ecossistema Rust
Crates populares
serde- Serialização (JSON, YAML, etc.)tokio- Runtime assíncronoactix-web/axum- Frameworks webclap- Parser de argumentos CLIreqwest- Cliente HTTPdiesel/sqlx- ORM e database
Adicionando dependências
cargo add serde --features derive
cargo add tokio --features full
Projetos Práticos
To-Do List CLI
Lista de tarefas no terminal com adição, remoção e listagem.
Conversor de Moedas
Converte valores entre moedas usando taxas de câmbio.
Jogo de Adivinhação
Adivinhe o número secreto com dicas de "maior" ou "menor".
API REST
CRUD de tarefas com Actix-web ou Axum.
CRUD com Persistência
Projeto final com banco de dados SQLite.
Carreira
Pratique sempre
Pequenos projetos valem mais que só leitura.
Participe de comunidades
Discord, Telegram, Reddit r/rust.
Contribua com open source
Comece com issues marcadas "good first issue".
Monte um portfólio
Publique projetos no GitHub com README bem escrito.
FAQ
Por que o compilador reclama do ownership?
Você está tentando usar um valor que foi movido para outra variável. Use .clone() ou referências &.
Qual a diferença entre String e &str?
String é dinâmica e vive no heap. &str é uma referência a dados de string (pode ser literal ou parte de String).
Como instalar dependências?
Use cargo add nome_da_crate ou adicione manualmente no Cargo.toml.
Como rodar testes?
cargo test executa todos os testes. Use cargo test nome para filtrar.