Glossário Rust
Referência completa dos termos e conceitos da linguagem Rust, explicados em português brasileiro. Cada entrada contém uma definição concisa e um exemplo de código prático.
Borrow Checker
O que é: O verificador de empréstimos do compilador Rust. Ele analisa o código em tempo de compilação para garantir que todas as referências sejam válidas e que as regras de ownership e borrowing sejam respeitadas. O borrow checker é responsável pela segurança de memória sem garbage collector.
Exemplo:
fn main() {
let mut dados = String::from("olá");
let r1 = &dados; // empréstimo imutável - ok
let r2 = &dados; // outro empréstimo imutável - ok
println!("{} {}", r1, r2);
// r1 e r2 não são mais usados depois deste ponto
let r3 = &mut dados; // empréstimo mutável - ok (r1 e r2 já saíram de uso)
r3.push_str(" mundo");
println!("{}", r3);
// O borrow checker impede isto:
// let r4 = &dados; // ERRO: não pode ter & e &mut ao mesmo tempo
// println!("{} {}", r3, r4);
}
Cargo
O que é: O gerenciador de pacotes e sistema de build oficial do Rust. O Cargo gerencia dependências, compila projetos, roda testes, gera documentação e publica crates. O arquivo de configuração é o Cargo.toml.
Exemplo:
// Cargo.toml
// [package]
// name = "meu_projeto"
// version = "0.1.0"
// edition = "2021"
//
// [dependencies]
// serde = { version = "1.0", features = ["derive"] }
// Comandos principais:
// cargo new meu_projeto - criar projeto
// cargo build - compilar
// cargo run - compilar e executar
// cargo test - rodar testes
// cargo doc --open - gerar documentação
// cargo clippy - análise estática
// cargo fmt - formatar código
Clone
O que é: Um trait que permite criar uma cópia profunda (deep copy) de um valor. Diferente de Copy, que copia bits automaticamente, Clone exige uma chamada explícita ao método .clone() e pode envolver alocações de memória.
Exemplo:
#[derive(Clone, Debug)]
struct Pessoa {
nome: String,
idade: u32,
}
fn main() {
let p1 = Pessoa {
nome: String::from("Ana"),
idade: 30,
};
let p2 = p1.clone(); // cópia profunda - String interna é duplicada no heap
println!("{:?}", p1); // p1 ainda é válido
println!("{:?}", p2); // p2 é uma cópia independente
}
Copy
O que é: Um marker trait que indica que um tipo pode ser copiado bit a bit automaticamente (sem necessidade de chamar .clone()). Tipos Copy são copiados quando atribuídos a outra variável, em vez de serem movidos. Apenas tipos que vivem inteiramente na stack podem implementar Copy (ex: inteiros, floats, bool, char, tuplas de tipos Copy).
Exemplo:
// i32 implementa Copy
let x = 42;
let y = x; // cópia automática, x ainda é válido
println!("{} {}", x, y); // funciona!
// String NÃO implementa Copy (dados no heap)
let s1 = String::from("olá");
let s2 = s1; // MOVE, não cópia
// println!("{}", s1); // ERRO: s1 foi movido
// Struct com Copy (todos os campos devem ser Copy)
#[derive(Copy, Clone, Debug)]
struct Ponto {
x: f64,
y: f64,
}
let a = Ponto { x: 1.0, y: 2.0 };
let b = a; // cópia automática
println!("{:?} {:?}", a, b); // ambos válidos
Crate
O que é: A unidade de compilação em Rust. Um crate pode ser uma biblioteca (library crate) ou um executável (binary crate). Cada crate tem uma raiz: src/lib.rs para bibliotecas ou src/main.rs para binários. Crates são publicados e compartilhados no crates.io.
Exemplo:
// Usando um crate externo (declarado no Cargo.toml)
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Config {
host: String,
porta: u16,
}
// Referenciando o crate atual
pub mod utils {
pub fn ajudar() {}
}
// De outro módulo no mesmo crate:
use crate::utils::ajudar;
Derive
O que é: Um atributo que gera automaticamente a implementação de traits para structs e enums. O compilador gera o código de implementação em tempo de compilação, evitando boilerplate. Crates externas podem fornecer derives customizados via macros procedurais.
Exemplo:
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct Usuario {
nome: String,
idade: u32,
}
fn main() {
let u1 = Usuario::default(); // Default
let u2 = Usuario {
nome: "Ana".into(),
idade: 30,
};
let u3 = u2.clone(); // Clone
println!("{:?}", u3); // Debug
assert_eq!(u2, u3); // PartialEq
// Derive de crate externo (serde)
// #[derive(serde::Serialize, serde::Deserialize)]
}
Drop
O que é: Um trait que permite executar código customizado quando um valor sai de escopo. O compilador chama o método drop() automaticamente. Usado para liberar recursos como arquivos, conexões de rede ou memória alocada manualmente. Nao é possível chamar drop() diretamente; use std::mem::drop() para forçar a liberação antecipada.
Exemplo:
struct Conexao {
nome: String,
}
impl Drop for Conexao {
fn drop(&mut self) {
println!("Fechando conexão: {}", self.nome);
}
}
fn main() {
let c1 = Conexao { nome: "banco".into() };
let c2 = Conexao { nome: "cache".into() };
println!("Conexões criadas");
drop(c1); // força a liberação antecipada
println!("c1 foi liberado");
// c2 é liberado automaticamente ao final do escopo
} // imprime: "Fechando conexão: cache"
dyn
O que é: Palavra-chave usada para indicar despacho dinâmico (dynamic dispatch) com trait objects. dyn Trait representa um tipo que implementa o trait, mas cujo tipo concreto é resolvido em tempo de execução via vtable, em vez de em tempo de compilação (despacho estático com impl Trait ou genéricos).
Exemplo:
trait Animal {
fn som(&self) -> &str;
}
struct Gato;
struct Cachorro;
impl Animal for Gato {
fn som(&self) -> &str { "miau" }
}
impl Animal for Cachorro {
fn som(&self) -> &str { "au au" }
}
// Despacho dinâmico: tipo concreto resolvido em runtime
fn fazer_barulho(animal: &dyn Animal) {
println!("{}", animal.som());
}
fn main() {
let animais: Vec<Box<dyn Animal>> = vec![
Box::new(Gato),
Box::new(Cachorro),
];
for animal in &animais {
fazer_barulho(animal.as_ref());
}
}
Enum
O que é: Um tipo algébrico que representa um valor que pode ser uma dentre várias variantes. Cada variante pode conter dados de tipos diferentes. Enums em Rust são muito mais poderosos que em outras linguagens, pois cada variante pode carregar dados associados. Option e Result são enums da biblioteca padrão.
Exemplo:
enum Forma {
Circulo(f64), // raio
Retangulo { largura: f64, altura: f64 },
Triangulo(f64, f64, f64), // três lados
Ponto, // sem dados
}
impl Forma {
fn area(&self) -> f64 {
match self {
Forma::Circulo(r) => std::f64::consts::PI * r * r,
Forma::Retangulo { largura, altura } => largura * altura,
Forma::Triangulo(a, b, c) => {
let s = (a + b + c) / 2.0;
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
Forma::Ponto => 0.0,
}
}
}
Fat Pointer
O que é: Um ponteiro que armazena informações adicionais além do endereço de memória. Em Rust, fat pointers possuem o dobro do tamanho de um ponteiro normal. Exemplos incluem slices (&[T]), que armazenam ponteiro + tamanho, e trait objects (&dyn Trait), que armazenam ponteiro para dados + ponteiro para vtable.
Exemplo:
use std::mem::size_of;
fn main() {
// Ponteiro normal: 8 bytes (em 64-bit)
println!("&i32: {} bytes", size_of::<&i32>()); // 8
// Fat pointer (slice): ponteiro + tamanho = 16 bytes
println!("&[i32]: {} bytes", size_of::<&[i32]>()); // 16
// Fat pointer (trait object): ponteiro + vtable = 16 bytes
println!("&dyn ToString: {} bytes",
size_of::<&dyn ToString>()); // 16
// &str também é um fat pointer (ponteiro + tamanho)
println!("&str: {} bytes", size_of::<&str>()); // 16
let dados = vec![1, 2, 3, 4, 5];
let slice: &[i32] = &dados[1..4]; // fat pointer: aponta para dados[1], len=3
}
Generics (Genéricos)
O que é: Mecanismo que permite escrever código que funciona com múltiplos tipos. Genéricos em Rust usam monomorfização: o compilador gera código especializado para cada tipo concreto usado, resultando em zero custo em tempo de execução (zero-cost abstraction).
Exemplo:
// Função genérica
fn maior<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
// Struct genérica
struct Par<T, U> {
primeiro: T,
segundo: U,
}
impl<T, U> Par<T, U> {
fn novo(primeiro: T, segundo: U) -> Self {
Par { primeiro, segundo }
}
}
// Implementação condicional
impl<T: std::fmt::Display, U: std::fmt::Display> Par<T, U> {
fn exibir(&self) {
println!("({}, {})", self.primeiro, self.segundo);
}
}
fn main() {
println!("{}", maior(10, 20)); // i32
println!("{}", maior(3.14, 2.72)); // f64
println!("{}", maior("abc", "xyz")); // &str
let p = Par::novo("idade", 30);
p.exibir();
}
impl
O que é: Palavra-chave com dois usos principais: (1) definir implementações (métodos e funções associadas) para tipos com blocos impl Tipo, e (2) em posição de tipo, como atalho para genéricos com trait bounds (impl Trait), usado em parâmetros e retornos de função.
Exemplo:
struct Contador {
valor: u32,
}
// (1) Bloco de implementação
impl Contador {
fn novo() -> Self {
Contador { valor: 0 }
}
fn incrementar(&mut self) {
self.valor += 1;
}
fn valor(&self) -> u32 {
self.valor
}
}
// (2) impl Trait em parâmetro (aceita qualquer tipo que implemente Display)
fn imprimir(item: impl std::fmt::Display) {
println!("{}", item);
}
// (2) impl Trait em retorno (retorna algum tipo que implemente Iterator)
fn pares() -> impl Iterator<Item = i32> {
(0..).filter(|x| x % 2 == 0)
}
Iterator
O que é: Um trait da biblioteca padrão que permite processar uma sequência de elementos um de cada vez. Requer apenas a implementação do método next(), que retorna Option<Self::Item>. O trait fornece dezenas de métodos adaptadores e consumidores (map, filter, fold, collect, etc.).
Exemplo:
struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn novo() -> Self {
Fibonacci { a: 0, b: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let proximo = self.a;
self.a = self.b;
self.b = proximo + self.b;
Some(proximo)
}
}
fn main() {
// Primeiros 10 números de Fibonacci
let fibs: Vec<u64> = Fibonacci::novo().take(10).collect();
println!("{:?}", fibs); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// Soma dos Fibonacci menores que 1000
let soma: u64 = Fibonacci::novo()
.take_while(|&x| x < 1000)
.sum();
println!("Soma: {}", soma);
}
Lifetime (Tempo de Vida)
O que é: Uma anotação que indica ao compilador por quanto tempo uma referência é válida. Lifetimes previnem referências pendentes (dangling references). O compilador infere a maioria dos lifetimes automaticamente (elisão de lifetimes), mas em casos ambíguos é necessário anotá-los explicitamente com a sintaxe 'a.
Exemplo:
// Sem anotação: o compilador infere
fn primeiro(s: &str) -> &str {
&s[..1]
}
// Com anotação: necessário quando há múltiplas referências de entrada
fn mais_longo<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Lifetime em struct: a struct não pode viver mais que a referência
struct Citacao<'a> {
texto: &'a str,
}
impl<'a> Citacao<'a> {
fn nova(texto: &'a str) -> Self {
Citacao { texto }
}
fn exibir(&self) {
println!("\"{}\"", self.texto);
}
}
fn main() {
let texto = String::from("Ser ou não ser");
let citacao = Citacao::nova(&texto);
citacao.exibir();
}
Macro
O que é: Recurso de metaprogramação que gera código em tempo de compilação. Rust possui dois tipos: macros declarativas (macro_rules!) que funcionam por correspondência de padrões, e macros procedurais (derive macros, attribute macros, function-like macros) que operam sobre a árvore sintática (token stream).
Exemplo:
// Macro declarativa (macro_rules!)
macro_rules! criar_vetor {
// Padrão: lista de expressões separadas por vírgula
( $( $elemento:expr ),* ) => {
{
let mut v = Vec::new();
$( v.push($elemento); )*
v
}
};
}
macro_rules! dizer {
($msg:expr) => {
println!("[LOG] {}", $msg);
};
($fmt:expr, $($arg:tt)*) => {
println!(concat!("[LOG] ", $fmt), $($arg)*);
};
}
fn main() {
let v = criar_vetor![1, 2, 3, 4, 5];
println!("{:?}", v);
dizer!("Iniciando");
dizer!("Valor: {}", 42);
}
// Macros procedurais são definidas em crates separados
// Ex: #[derive(Serialize)] é uma macro procedural do serde
Match
O que é: Expressão de controle de fluxo que compara um valor contra uma série de padrões e executa o código do primeiro padrão que corresponde. O match em Rust é exaustivo: todos os casos possíveis devem ser cobertos. Suporta desestruturação, guards, ranges e binding.
Exemplo:
enum Resultado {
Sucesso(String),
Erro(i32, String),
EmProgresso,
}
fn processar(r: Resultado) {
match r {
// Desestruturação com binding
Resultado::Sucesso(msg) => println!("OK: {}", msg),
// Guard (condição extra)
Resultado::Erro(codigo, msg) if codigo >= 500 => {
eprintln!("Erro crítico {}: {}", codigo, msg);
}
// Múltiplos padrões com |
Resultado::Erro(404, _) | Resultado::Erro(410, _) => {
println!("Recurso não encontrado");
}
// Binding com @
Resultado::Erro(code @ 400..=499, msg) => {
println!("Erro do cliente {}: {}", code, msg);
}
// Wildcard
Resultado::Erro(code, msg) => {
println!("Erro {}: {}", code, msg);
}
Resultado::EmProgresso => println!("Aguardando..."),
}
}
Module (Módulo)
O que é: Unidade de organização de código em Rust. Módulos controlam a visibilidade (público/privado) e o namespace dos itens. Podem ser definidos inline, em arquivos separados (modulo.rs) ou em diretórios (modulo/mod.rs). A palavra-chave pub torna itens visíveis fora do módulo.
Exemplo:
// src/lib.rs
pub mod autenticacao {
// Público - acessível de fora
pub struct Usuario {
pub nome: String,
senha_hash: String, // privado - só acessível dentro do módulo
}
impl Usuario {
pub fn novo(nome: &str, senha: &str) -> Self {
Usuario {
nome: nome.into(),
senha_hash: hash(senha),
}
}
pub fn verificar(&self, senha: &str) -> bool {
self.senha_hash == hash(senha)
}
}
// Função privada do módulo
fn hash(senha: &str) -> String {
format!("hash_{}", senha) // simplificado
}
}
// Uso
use autenticacao::Usuario;
fn main() {
let user = Usuario::novo("admin", "s3cret");
println!("Bem-vindo, {}", user.nome);
}
Move
O que é: A transferência de ownership de um valor para outro binding ou escopo. Quando um tipo que nao implementa Copy é atribuído a outra variável ou passado como argumento, o valor é movido – o binding original se torna inválido. A palavra-chave move também é usada com closures para forçar a captura por valor.
Exemplo:
fn main() {
// Move em atribuição
let s1 = String::from("olá");
let s2 = s1; // s1 é movido para s2
// println!("{}", s1); // ERRO: valor foi movido
// Move em chamada de função
let dados = vec![1, 2, 3];
consumir(dados);
// println!("{:?}", dados); // ERRO: dados foi movido
// Closure com move (captura por valor)
let nome = String::from("Rust");
let saudar = move || {
println!("Olá, {}!", nome);
// nome foi movido para dentro da closure
};
// println!("{}", nome); // ERRO: nome foi movido para a closure
saudar();
}
fn consumir(v: Vec<i32>) {
println!("Consumido: {:?}", v);
} // v é descartado aqui
Mutex
O que é: Primitive de sincronização (mutual exclusion) que permite acesso exclusivo a dados compartilhados entre threads. Para acessar os dados, a thread deve adquirir o lock. Em Rust, Mutex<T> garante em tempo de compilação que os dados só podem ser acessados com o lock adquirido. Geralmente usado em combinação com Arc<T>.
Exemplo:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let contador = Arc::new(Mutex::new(0));
let mut handles = vec![];
for i in 0..5 {
let contador = Arc::clone(&contador);
let handle = thread::spawn(move || {
let mut num = contador.lock().unwrap();
*num += 1;
println!("Thread {} incrementou para {}", i, *num);
}); // lock é liberado aqui (MutexGuard é dropado)
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Total: {}", *contador.lock().unwrap());
}
Option
O que é: Enum da biblioteca padrão que representa um valor que pode ou não existir: Some(T) contém um valor, None indica ausência. É a alternativa do Rust a valores nulos (null/nil) de outras linguagens, forçando o tratamento explícito da ausência de valor.
Exemplo:
fn buscar_usuario(id: u32) -> Option<String> {
match id {
1 => Some("Ana".into()),
2 => Some("Bruno".into()),
_ => None,
}
}
fn main() {
// Pattern matching
match buscar_usuario(1) {
Some(nome) => println!("Encontrado: {}", nome),
None => println!("Usuário não encontrado"),
}
// Encadeamento funcional
let tamanho = buscar_usuario(2)
.map(|nome| nome.len())
.unwrap_or(0);
// unwrap_or_else com closure
let nome = buscar_usuario(99)
.unwrap_or_else(|| "Anônimo".into());
// if let
if let Some(nome) = buscar_usuario(1) {
println!("Olá, {}!", nome);
}
// ? operator (em funções que retornam Option)
let resultado = (|| -> Option<usize> {
let nome = buscar_usuario(1)?;
Some(nome.len())
})();
}
Ownership (Posse)
O que é: O sistema central de gerenciamento de memória do Rust. Cada valor tem exatamente um dono (owner). Quando o dono sai de escopo, o valor é automaticamente liberado (drop). Ownership pode ser transferida (move) ou emprestada temporariamente (borrow). Este sistema elimina a necessidade de garbage collector e previne memory leaks, double free e use-after-free.
Exemplo:
fn main() {
// 1. Cada valor tem um dono
let s = String::from("olá"); // s é o dono
// 2. Transferência de ownership (move)
let s2 = s; // ownership transferida para s2
// println!("{}", s); // ERRO: s não é mais o dono
// 3. Empréstimo (borrow) - não transfere ownership
let s3 = String::from("mundo");
imprimir(&s3); // empréstimo imutável
println!("{}", s3); // s3 ainda é válido
// 4. Quando o dono sai de escopo, o valor é liberado
{
let temporario = String::from("efêmero");
println!("{}", temporario);
} // temporario é liberado aqui (drop)
// 5. Retorno devolve ownership
let s4 = criar_string(); // ownership transferida para s4
println!("{}", s4);
} // s2, s3, s4 são liberados aqui
fn imprimir(s: &String) {
println!("{}", s);
}
fn criar_string() -> String {
String::from("nova string") // ownership é retornada
}
Panic
O que é: Encerramento abrupto de uma thread causado por um erro irrecuperável. Quando ocorre um panic, o Rust faz o unwind da stack (ou abort, dependendo da configuração), chamando os destructors (Drop) de cada valor. Pode ser disparado explicitamente com panic!(), ou implicitamente por operações como acesso fora dos limites de um array ou .unwrap() em None/Err.
Exemplo:
fn main() {
// Panic explícito
// panic!("algo deu muito errado!");
// Panic por acesso fora dos limites
let v = vec![1, 2, 3];
// let x = v[10]; // panic: index out of bounds
// Panic por unwrap em None
let vazio: Option<i32> = None;
// vazio.unwrap(); // panic: called unwrap() on None
// Capturando panic (para testes ou isolamento)
let resultado = std::panic::catch_unwind(|| {
panic!("capturado!");
});
assert!(resultado.is_err());
println!("Programa continua após panic capturado");
// Use expect() para panics com mensagens descritivas
let config = std::env::var("APP_CONFIG")
.expect("A variável APP_CONFIG deve estar definida");
}
Pattern Matching (Correspondência de Padrões)
O que é: Recurso poderoso do Rust para desestruturar e comparar valores contra padrões. Usado em match, if let, while let, let e parâmetros de função. Padrões podem incluir literais, variáveis, wildcards (_), ranges, referências, structs, enums e guards.
Exemplo:
struct Ponto { x: i32, y: i32 }
enum Comando {
Mover(Ponto),
Cor(u8, u8, u8),
Mensagem(String),
}
fn processar(cmd: &Comando) {
match cmd {
// Desestruturar struct dentro de enum
Comando::Mover(Ponto { x, y }) => {
println!("Mover para ({}, {})", x, y);
}
// Desestruturar tupla com guard
Comando::Cor(r, g, b) if *r > 200 => {
println!("Cor avermelhada: ({}, {}, {})", r, g, b);
}
// Binding com @
Comando::Cor(r @ 0..=50, g @ 0..=50, b @ 0..=50) => {
println!("Cor escura: ({}, {}, {})", r, g, b);
}
// Referência a String
Comando::Mensagem(msg) if msg.is_empty() => {
println!("Mensagem vazia");
}
// Catch-all
_ => println!("Outro comando"),
}
}
fn main() {
// let com desestruturação
let (a, b, c) = (1, 2, 3);
let Ponto { x, y } = Ponto { x: 10, y: 20 };
// if let
let valor: Option<i32> = Some(42);
if let Some(n) = valor {
println!("Valor: {}", n);
}
// Desestruturação em for
let pontos = vec![Ponto { x: 1, y: 2 }, Ponto { x: 3, y: 4 }];
for Ponto { x, y } in &pontos {
println!("({}, {})", x, y);
}
}
Pin
O que é: Um wrapper que impede que um valor seja movido na memória. Necessário para tipos self-referential (que contêm ponteiros para si mesmos), como futures geradas por async/await. Pin<P> garante que o valor apontado por P não será movido, permitindo que referências internas permaneçam válidas.
Exemplo:
use std::pin::Pin;
use std::marker::PhantomPinned;
// Struct que não pode ser movida depois de pinned
struct NaoMova {
dados: String,
// Tornando o tipo !Unpin (não pode ser despinned)
_pin: PhantomPinned,
}
impl NaoMova {
fn novo(dados: &str) -> Self {
NaoMova {
dados: dados.into(),
_pin: PhantomPinned,
}
}
}
fn main() {
// Pin no heap (mais comum na prática)
let pinned = Box::pin(NaoMova::novo("dados fixos"));
println!("{}", pinned.dados);
// Pin é essencial para futures (async/await)
// O compilador gera código que usa Pin internamente
// async fn exemplo() -> i32 {
// let x = 42;
// alguma_future().await; // pode ser suspenso aqui
// x // x não pode ser movido
// }
}
Rc / Arc
O que é: Smart pointers de contagem de referências que permitem múltiplos owners para o mesmo valor. Rc<T> (Reference Counted) é para uso em single-thread; Arc<T> (Atomically Reference Counted) é thread-safe. O valor é liberado quando o último owner é descartado (contador chega a zero). Ambos fornecem apenas acesso imutável; para mutação, combine com RefCell (Rc) ou Mutex (Arc).
Exemplo:
use std::rc::Rc;
use std::sync::Arc;
use std::thread;
fn main() {
// Rc - single-thread
let dados = Rc::new(vec![1, 2, 3]);
let ref1 = Rc::clone(&dados);
let ref2 = Rc::clone(&dados);
println!("Refs: {}", Rc::strong_count(&dados)); // 3
println!("{:?} {:?} {:?}", dados, ref1, ref2);
// Arc - multi-thread
let compartilhado = Arc::new(String::from("dados compartilhados"));
let mut handles = vec![];
for i in 0..3 {
let dados = Arc::clone(&compartilhado);
handles.push(thread::spawn(move || {
println!("Thread {}: {}", i, dados);
}));
}
for h in handles {
h.join().unwrap();
}
}
Result
O que é: Enum da biblioteca padrão para tratamento de erros recuperáveis: Ok(T) indica sucesso com um valor, Err(E) indica falha com informação do erro. É o mecanismo principal de tratamento de erros em Rust, usado com o operador ? para propagação conveniente de erros.
Exemplo:
use std::fs;
use std::io;
#[derive(Debug)]
enum AppErro {
Io(io::Error),
Parse(std::num::ParseIntError),
Validacao(String),
}
impl From<io::Error> for AppErro {
fn from(e: io::Error) -> Self { AppErro::Io(e) }
}
impl From<std::num::ParseIntError> for AppErro {
fn from(e: std::num::ParseIntError) -> Self { AppErro::Parse(e) }
}
fn ler_porta(caminho: &str) -> Result<u16, AppErro> {
let conteudo = fs::read_to_string(caminho)?; // ? converte io::Error
let porta: u16 = conteudo.trim().parse()?; // ? converte ParseIntError
if porta < 1024 {
return Err(AppErro::Validacao(
format!("Porta {} é reservada", porta)
));
}
Ok(porta)
}
fn main() {
match ler_porta("porta.txt") {
Ok(porta) => println!("Porta: {}", porta),
Err(AppErro::Io(e)) => eprintln!("Erro de IO: {}", e),
Err(AppErro::Parse(e)) => eprintln!("Erro de parse: {}", e),
Err(AppErro::Validacao(msg)) => eprintln!("Validação: {}", msg),
}
}
Send / Sync
O que é: Marker traits automáticos que controlam a segurança entre threads. Send indica que um tipo pode ser transferido (moved) entre threads com segurança. Sync indica que um tipo pode ser compartilhado (referenciado) entre threads com segurança. A maioria dos tipos implementa ambos automaticamente. Tipos como Rc<T> e RefCell<T> nao implementam Send/Sync.
Exemplo:
use std::thread;
use std::sync::{Arc, Mutex};
use std::rc::Rc;
fn main() {
// String é Send + Sync
let s = String::from("olá");
thread::spawn(move || {
println!("{}", s); // OK: String pode ser enviada entre threads
}).join().unwrap();
// Rc NÃO é Send (não é thread-safe)
let rc = Rc::new(42);
// thread::spawn(move || {
// println!("{}", rc); // ERRO: Rc não implementa Send
// });
// Arc É Send + Sync (thread-safe)
let arc = Arc::new(42);
let arc_clone = Arc::clone(&arc);
thread::spawn(move || {
println!("{}", arc_clone); // OK
}).join().unwrap();
// Mutex<T> torna T seguro para acesso entre threads
let dados = Arc::new(Mutex::new(vec![1, 2, 3]));
let dados_clone = Arc::clone(&dados);
thread::spawn(move || {
dados_clone.lock().unwrap().push(4); // OK: acesso exclusivo
}).join().unwrap();
}
Slice
O que é: Uma visão (view) contígua sobre uma sequência de elementos em memória, sem ownership. Representado como &[T] (imutável) ou &mut [T] (mutável). Um slice é um fat pointer que contém o endereço de início e o número de elementos. &str é um slice de bytes UTF-8.
Exemplo:
fn main() {
let v = vec![10, 20, 30, 40, 50];
// Criando slices
let todos: &[i32] = &v; // slice de todo o vetor
let meio: &[i32] = &v[1..4]; // [20, 30, 40]
let inicio: &[i32] = &v[..3]; // [10, 20, 30]
let fim: &[i32] = &v[2..]; // [30, 40, 50]
// Funções que recebem slices são mais flexíveis
fn soma(numeros: &[i32]) -> i32 {
numeros.iter().sum()
}
// Aceita Vec, array ou outro slice
println!("{}", soma(&v)); // Vec
println!("{}", soma(&[1, 2, 3])); // array
println!("{}", soma(meio)); // slice
// Métodos de slice
let primeiro = meio.first(); // Option<&i32>
let ultimo = meio.last(); // Option<&i32>
let contem = meio.contains(&30); // true
let vazio = meio.is_empty(); // false
// Slice mutável
let mut dados = vec![3, 1, 4, 1, 5];
let s: &mut [i32] = &mut dados;
s.sort();
s.reverse();
println!("{:?}", s); // [5, 4, 3, 1, 1]
}
Smart Pointer (Ponteiro Inteligente)
O que é: Tipos que se comportam como ponteiros mas possuem funcionalidade adicional, como contagem de referências ou verificação de empréstimos em runtime. Implementam os traits Deref (para se comportar como referência) e Drop (para limpeza ao sair de escopo). Exemplos: Box<T>, Rc<T>, Arc<T>, RefCell<T>, Mutex<T>, Cow<T>.
Exemplo:
use std::borrow::Cow;
fn processar_texto(input: &str) -> Cow<str> {
if input.contains("ruim") {
// Precisa modificar: aloca nova String
Cow::Owned(input.replace("ruim", "bom"))
} else {
// Sem modificação: apenas referência, sem alocação
Cow::Borrowed(input)
}
}
fn main() {
// Cow (Clone on Write) - evita cópias desnecessárias
let texto1 = processar_texto("dia bom"); // Borrowed (sem cópia)
let texto2 = processar_texto("dia ruim"); // Owned (cópia necessária)
println!("{}", texto1); // "dia bom"
println!("{}", texto2); // "dia bom"
// Box: alocação no heap
let boxed: Box<i32> = Box::new(42);
println!("{}", *boxed); // dereferência
// String é um smart pointer para str
let s = String::from("olá"); // dados no heap
let r: &str = &s; // Deref coercion: String -> &str
}
Static Dispatch (Despacho Estático)
O que é: Resolução de chamadas de método em tempo de compilação. Quando se usam genéricos (<T: Trait>) ou impl Trait, o compilador gera código especializado (monomorfização) para cada tipo concreto usado. Resulta em chamadas diretas de função, sem overhead em runtime. Contraponto: dynamic dispatch.
Exemplo:
trait Saudacao {
fn saudar(&self) -> String;
}
struct Brasileiro;
struct Americano;
impl Saudacao for Brasileiro {
fn saudar(&self) -> String { "Olá!".into() }
}
impl Saudacao for Americano {
fn saudar(&self) -> String { "Hello!".into() }
}
// DESPACHO ESTÁTICO: o compilador gera uma versão para cada tipo
fn cumprimentar_estatico(pessoa: impl Saudacao) {
println!("{}", pessoa.saudar());
}
// Equivalente a gerar:
// fn cumprimentar_estatico_Brasileiro(pessoa: Brasileiro) { ... }
// fn cumprimentar_estatico_Americano(pessoa: Americano) { ... }
fn main() {
cumprimentar_estatico(Brasileiro); // chamada direta, sem vtable
cumprimentar_estatico(Americano); // chamada direta, sem vtable
}
Dynamic Dispatch (Despacho Dinâmico)
O que é: Resolução de chamadas de método em tempo de execução, usando uma vtable (tabela de ponteiros de função). Ocorre quando se usa dyn Trait. Permite armazenar tipos diferentes na mesma coleção, mas tem um pequeno custo de performance por indireção. Necessário quando o tipo concreto não é conhecido em tempo de compilação.
Exemplo:
trait Saudacao {
fn saudar(&self) -> String;
}
struct Brasileiro;
struct Americano;
impl Saudacao for Brasileiro {
fn saudar(&self) -> String { "Olá!".into() }
}
impl Saudacao for Americano {
fn saudar(&self) -> String { "Hello!".into() }
}
// DESPACHO DINÂMICO: resolução via vtable em runtime
fn cumprimentar_dinamico(pessoa: &dyn Saudacao) {
println!("{}", pessoa.saudar()); // chamada via vtable
}
fn main() {
// Coleção heterogênea (só possível com despacho dinâmico)
let pessoas: Vec<Box<dyn Saudacao>> = vec![
Box::new(Brasileiro),
Box::new(Americano),
];
for pessoa in &pessoas {
cumprimentar_dinamico(pessoa.as_ref());
}
}
Struct
O que é: Um tipo composto que agrupa valores relacionados sob um mesmo nome. Rust possui três tipos de structs: structs clássicas (com campos nomeados), tuple structs (com campos posicionais) e unit structs (sem campos). Structs são o principal bloco de construção para criar tipos customizados em Rust.
Exemplo:
// Struct clássica
struct Servidor {
host: String,
porta: u16,
ativo: bool,
}
// Tuple struct (útil para newtype pattern)
struct Celsius(f64);
struct Fahrenheit(f64);
impl Celsius {
fn para_fahrenheit(&self) -> Fahrenheit {
Fahrenheit(self.0 * 9.0 / 5.0 + 32.0)
}
}
// Unit struct (útil como marcador)
struct Producao;
struct Desenvolvimento;
trait Ambiente {
fn url_base(&self) -> &str;
}
impl Ambiente for Producao {
fn url_base(&self) -> &str { "https://api.exemplo.com" }
}
impl Ambiente for Desenvolvimento {
fn url_base(&self) -> &str { "http://localhost:3000" }
}
fn main() {
let temp = Celsius(100.0);
let f = temp.para_fahrenheit();
println!("{}°C = {}°F", temp.0, f.0); // 100°C = 212°F
}
Trait
O que é: Um conjunto de métodos que define um comportamento compartilhado. Similar a interfaces em outras linguagens, mas com a capacidade de fornecer implementações padrão. Traits são a base do polimorfismo em Rust e permitem bounds em genéricos, trait objects (despacho dinâmico), e podem ser derivados automaticamente.
Exemplo:
// Definir trait com método obrigatório e método padrão
trait Armazenavel {
fn chave(&self) -> String;
fn serializar(&self) -> String {
format!("{}={}", self.chave(), self.valor_padrao())
}
fn valor_padrao(&self) -> &str {
"vazio"
}
}
// Supertraits (trait que requer outros traits)
trait Exportavel: Armazenavel + std::fmt::Debug {
fn exportar(&self) -> String {
format!("{:?} -> {}", self, self.serializar())
}
}
#[derive(Debug)]
struct Documento {
id: u32,
titulo: String,
}
impl Armazenavel for Documento {
fn chave(&self) -> String {
format!("doc:{}", self.id)
}
fn valor_padrao(&self) -> &str {
&self.titulo
}
}
impl Exportavel for Documento {}
fn main() {
let doc = Documento { id: 1, titulo: "Relatório".into() };
println!("{}", doc.serializar()); // doc:1=Relatório
println!("{}", doc.exportar());
}
Trait Object (Objeto Trait)
O que é: Um tipo que representa qualquer valor que implemente um determinado trait, usando despacho dinâmico. Escrito como dyn Trait, é sempre usado atrás de algum tipo de ponteiro (&dyn Trait, Box<dyn Trait>, Arc<dyn Trait>). Permite polimorfismo em tempo de execução e coleções heterogêneas.
Exemplo:
trait Plugin {
fn nome(&self) -> &str;
fn executar(&self, entrada: &str) -> String;
}
struct Maiusculo;
struct Inversor;
impl Plugin for Maiusculo {
fn nome(&self) -> &str { "Maiúsculo" }
fn executar(&self, entrada: &str) -> String { entrada.to_uppercase() }
}
impl Plugin for Inversor {
fn nome(&self) -> &str { "Inversor" }
fn executar(&self, entrada: &str) -> String {
entrada.chars().rev().collect()
}
}
// Registro de plugins com trait objects
struct Motor {
plugins: Vec<Box<dyn Plugin>>,
}
impl Motor {
fn novo() -> Self {
Motor { plugins: vec![] }
}
fn adicionar(&mut self, plugin: Box<dyn Plugin>) {
self.plugins.push(plugin);
}
fn processar(&self, texto: &str) -> Vec<String> {
self.plugins.iter()
.map(|p| {
println!("Executando plugin: {}", p.nome());
p.executar(texto)
})
.collect()
}
}
fn main() {
let mut motor = Motor::novo();
motor.adicionar(Box::new(Maiusculo));
motor.adicionar(Box::new(Inversor));
let resultados = motor.processar("olá mundo");
println!("{:?}", resultados); // ["OLÁ MUNDO", "odnum álo"]
}
Turbofish
O que é: Sintaxe ::<Tipo> usada para especificar tipos genéricos explicitamente quando o compilador não consegue inferi-los. O nome vem da aparência visual ::<>, que lembra um peixe. É comumente usado com .parse(), .collect() e funções genéricas.
Exemplo:
fn main() {
// Turbofish em parse
let numero = "42".parse::<i32>().unwrap();
let float = "3.14".parse::<f64>().unwrap();
// Turbofish em collect
let lista = (0..5).collect::<Vec<i32>>();
let conjunto = [1, 2, 3].iter().collect::<std::collections::HashSet<_>>();
// Alternativa: anotar o tipo na variável (sem turbofish)
let numero: i32 = "42".parse().unwrap();
let lista: Vec<i32> = (0..5).collect();
// Turbofish em função genérica
fn identidade<T>(valor: T) -> T { valor }
let x = identidade::<i32>(42);
// Turbofish com múltiplos parâmetros
fn par<A, B>(a: A, b: B) -> (A, B) { (a, b) }
let p = par::<&str, i32>("idade", 30);
// Turbofish em chamada de método
let bytes = Vec::<u8>::with_capacity(1024);
}
Type Inference (Inferência de Tipos)
O que é: A capacidade do compilador Rust de deduzir automaticamente os tipos das variáveis e expressões com base no contexto de uso, sem necessidade de anotação explícita. O Rust usa o algoritmo Hindley-Milner. A inferência funciona localmente (dentro de funções); assinaturas de função sempre exigem tipos explícitos.
Exemplo:
fn main() {
// O compilador infere os tipos
let x = 42; // inferido como i32 (padrão para inteiros)
let y = 3.14; // inferido como f64 (padrão para floats)
let ativo = true; // inferido como bool
let nome = "Rust"; // inferido como &str
// Inferência pelo uso posterior
let mut v = Vec::new(); // tipo ainda desconhecido
v.push(42); // agora o compilador sabe: Vec<i32>
// Inferência em closures
let dobrar = |x| x * 2; // tipo de x inferido pelo uso
let resultado = dobrar(5); // x: i32 inferido aqui
// Quando a inferência não é suficiente, anote
let numero: i64 = 42; // explicitamente i64, não i32
let v: Vec<f64> = Vec::new(); // tipo explícito necessário
// Inferência em genéricos
let parsed = "42".parse::<i32>(); // turbofish quando necessário
}
Unsafe
O que é: Bloco ou função que permite operações que o compilador não pode verificar como seguras. Dentro de unsafe, é possível: desreferenciar ponteiros raw, chamar funções unsafe, acessar variáveis mutáveis estáticas, implementar traits unsafe e acessar campos de unions. O código unsafe não desabilita o borrow checker; apenas permite essas operações adicionais.
Exemplo:
fn main() {
// 1. Desreferenciar ponteiro raw
let mut valor = 42;
let ptr = &mut valor as *mut i32;
unsafe {
*ptr = 100;
println!("Valor: {}", *ptr);
}
// 2. Chamar função unsafe
unsafe {
let layout = std::alloc::Layout::new::<i32>();
let raw = std::alloc::alloc(layout) as *mut i32;
*raw = 42;
println!("Alocação manual: {}", *raw);
std::alloc::dealloc(raw as *mut u8, layout);
}
// 3. FFI (Foreign Function Interface)
extern "C" {
fn abs(input: i32) -> i32;
}
unsafe {
println!("abs(-5) = {}", abs(-5));
}
}
// Função unsafe
unsafe fn operacao_perigosa(ptr: *const i32) -> i32 {
*ptr // desreferenciando ponteiro raw
}
// Trait unsafe
unsafe trait MarcadorUnsafe {}
unsafe impl MarcadorUnsafe for i32 {}
Vec
O que é: O tipo de vetor dinâmico (growable array) da biblioteca padrão. Vec<T> armazena elementos contíguos no heap e cresce automaticamente conforme necessário. É a coleção mais usada em Rust. Oferece acesso indexado O(1), push/pop amortizado O(1) e suporta iteração, fatiamento (slicing) e diversos métodos utilitários.
Exemplo:
fn main() {
// Criação
let mut v = vec![1, 2, 3, 4, 5];
let zeros = vec![0; 10];
let mut vazio: Vec<String> = Vec::new();
let com_capacidade = Vec::<i32>::with_capacity(100);
// Modificação
v.push(6);
v.pop(); // Some(6)
v.insert(0, 0); // inserir no início
v.remove(0); // remover do início
v.extend([6, 7, 8]);
v.retain(|&x| x % 2 != 0); // manter apenas ímpares
// Acesso
let primeiro = v[0]; // panic se vazio
let seguro = v.get(0); // Option<&i32>
let fatia = &v[1..3]; // slice &[i32]
// Transformação
v.sort();
v.reverse();
v.dedup(); // remover duplicatas consecutivas
v.truncate(3); // manter apenas 3 elementos
// Conversões
let s: &[i32] = &v; // Vec -> slice
let v2: Vec<i32> = s.to_vec(); // slice -> Vec
let array: [i32; 3] = v.try_into().unwrap(); // Vec -> array (se tamanho correto)
// Iteração com drain (remove e itera)
let removidos: Vec<i32> = v.drain(0..2).collect();
}
Zero-Cost Abstraction (Abstração de Custo Zero)
O que é: Princípio fundamental do Rust herdado do C++: abstrações de alto nível (iteradores, generics, traits, closures) não possuem overhead em tempo de execução comparado ao código de baixo nível equivalente escrito manualmente. O compilador otimiza o código abstrato para ser tão eficiente quanto o código imperativo.
Exemplo:
fn main() {
let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Código funcional com iteradores (zero-cost abstraction)
let soma_pares_dobrados: i32 = numeros.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * 2)
.sum();
// O compilador gera código equivalente a este loop manual:
let mut soma_manual: i32 = 0;
for &x in &numeros {
if x % 2 == 0 {
soma_manual += x * 2;
}
}
assert_eq!(soma_pares_dobrados, soma_manual); // 60
// Genéricos são monomorphizados (zero overhead)
fn dobrar<T: std::ops::Mul<Output = T> + From<u8>>(x: T) -> T {
x * T::from(2)
}
// O compilador gera versões especializadas:
// fn dobrar_i32(x: i32) -> i32 { x * 2 }
// fn dobrar_f64(x: f64) -> f64 { x * 2.0 }
println!("{}", dobrar(21i32)); // 42
println!("{}", dobrar(21.0f64)); // 42.0
}
Closure (Closure/Fechamento)
O que é: Uma função anônima que pode capturar variáveis do escopo onde é definida. Closures em Rust inferem os tipos dos parâmetros e retorno automaticamente. Implementam um dos traits Fn, FnMut ou FnOnce, dependendo de como capturam as variáveis (por referência imutável, mutável ou por valor/move).
Exemplo:
fn main() {
let multiplicador = 3;
// Fn: captura por referência imutável
let multiplicar = |x: i32| x * multiplicador;
println!("{}", multiplicar(5)); // 15
println!("{}", multiplicar(10)); // 30 (pode ser chamada várias vezes)
// FnMut: captura por referência mutável
let mut total = 0;
let mut acumular = |x: i32| {
total += x;
total
};
println!("{}", acumular(5)); // 5
println!("{}", acumular(3)); // 8
// FnOnce: captura por valor (move)
let nome = String::from("Rust");
let saudar = move || {
println!("Olá, {}!", nome); // nome foi movido
};
saudar();
// println!("{}", nome); // ERRO: nome foi movido
// Closure como retorno de função
fn criar_filtro(minimo: i32) -> impl Fn(i32) -> bool {
move |x| x >= minimo
}
let filtro = criar_filtro(18);
println!("{}", filtro(20)); // true
println!("{}", filtro(15)); // false
}
Crate Feature
O que é: Flags de compilação condicional definidas no Cargo.toml que permitem habilitar ou desabilitar funcionalidades de um crate. Features permitem que crates ofereçam funcionalidades opcionais sem impor dependências desnecessárias. São ativadas pelo consumidor do crate.
Exemplo:
// Cargo.toml
// [features]
// default = ["json"]
// json = ["dep:serde_json"]
// xml = ["dep:quick-xml"]
// full = ["json", "xml"]
//
// [dependencies]
// serde_json = { version = "1.0", optional = true }
// quick-xml = { version = "0.31", optional = true }
// No código:
#[cfg(feature = "json")]
pub fn para_json<T: serde::Serialize>(valor: &T) -> String {
serde_json::to_string(valor).unwrap()
}
#[cfg(feature = "xml")]
pub fn para_xml<T: serde::Serialize>(valor: &T) -> String {
// serialização XML
todo!()
}
// Uso pelo consumidor:
// cargo add meu_crate --features json,xml
// ou no Cargo.toml:
// meu_crate = { version = "1.0", features = ["json", "xml"] }
From / Into
O que é: Par de traits para conversão entre tipos. From<T> define como criar um tipo a partir de outro. Implementar From automaticamente fornece Into na direção oposta. São preferidos sobre as para conversões que podem falhar (usando TryFrom/TryInto) e são amplamente usados com o operador ? para conversão de erros.
Exemplo:
struct Email(String);
impl From<&str> for Email {
fn from(s: &str) -> Self {
Email(s.to_string())
}
}
impl From<String> for Email {
fn from(s: String) -> Self {
Email(s)
}
}
// TryFrom para conversões que podem falhar
impl TryFrom<&str> for PortaValida {
type Error = String;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let porta: u16 = s.parse().map_err(|_| "porta inválida".to_string())?;
if porta > 0 {
Ok(PortaValida(porta))
} else {
Err("porta deve ser maior que 0".into())
}
}
}
struct PortaValida(u16);
fn main() {
// From
let email1 = Email::from("user@exemplo.com");
// Into (funciona automaticamente por causa do From)
let email2: Email = "admin@exemplo.com".into();
// Muito usado em parâmetros de função
fn enviar(para: impl Into<Email>, msg: &str) {
let email = para.into();
println!("Enviando '{}' para {}", msg, email.0);
}
enviar("user@exemplo.com", "Olá!");
enviar(String::from("admin@exemplo.com"), "Aviso");
}
AsRef / AsMut
O que é: Traits para conversões baratas (sem cópia) de referência. AsRef<T> converte &Self em &T, e AsMut<T> converte &mut Self em &mut T. Muito usados em parâmetros de função para aceitar múltiplos tipos de forma ergonômica (por exemplo, aceitar tanto String quanto &str).
Exemplo:
use std::path::Path;
// AsRef permite aceitar String, &str, &String, etc.
fn contar_caracteres(texto: impl AsRef<str>) -> usize {
texto.as_ref().chars().count()
}
// Aceita &str, String, &Path, PathBuf, etc.
fn existe(caminho: impl AsRef<Path>) -> bool {
caminho.as_ref().exists()
}
fn main() {
// Todos estes funcionam:
println!("{}", contar_caracteres("olá"));
println!("{}", contar_caracteres(String::from("olá")));
println!("{}", contar_caracteres(&String::from("olá")));
// Com caminhos:
existe("/tmp");
existe(String::from("/tmp"));
existe(Path::new("/tmp"));
}
Deref Coercion
O que é: Conversão automática que o compilador aplica quando o tipo de uma referência não corresponde ao esperado. Se um tipo implementa Deref<Target = U>, o compilador converte automaticamente &T em &U. Funciona em cadeia: &String -> &str, &Vec<T> -> &[T], &Box<T> -> &T.
Exemplo:
use std::ops::Deref;
struct MinhaString {
dados: String,
}
impl Deref for MinhaString {
type Target = str;
fn deref(&self) -> &str {
&self.dados
}
}
fn imprimir(s: &str) {
println!("{}", s);
}
fn main() {
let ms = MinhaString { dados: "olá".into() };
imprimir(&ms); // MinhaString -> &str (Deref coercion)
let s = String::from("mundo");
imprimir(&s); // String -> &str (Deref coercion)
let boxed = Box::new(String::from("!"));
imprimir(&boxed); // Box<String> -> String -> &str (cadeia)
// Deref coercion acontece automaticamente em:
// - Passagem de argumentos para funções
// - Chamadas de método (method resolution)
// - Acesso a campos via .
}
Lifetime Elision (Elisão de Lifetimes)
O que é: Conjunto de regras que permite ao compilador inferir lifetimes automaticamente em assinaturas de funções, evitando anotações explícitas na maioria dos casos. São três regras: (1) cada parâmetro de referência recebe seu próprio lifetime, (2) se há exatamente um lifetime de entrada, ele é atribuído à saída, (3) se um parâmetro é &self ou &mut self, seu lifetime é usado na saída.
Exemplo:
// O compilador aplica as regras de elisão automaticamente:
// Regra 2: um lifetime de entrada -> usado na saída
fn primeiro_char(s: &str) -> &str { // inferido como: <'a>(s: &'a str) -> &'a str
&s[..1]
}
// Regra 3: &self -> seu lifetime é usado na saída
struct Parser {
conteudo: String,
}
impl Parser {
// Inferido como: <'a>(&'a self) -> &'a str
fn conteudo(&self) -> &str {
&self.conteudo
}
}
// PRECISA de anotação (regra 1: dois lifetimes diferentes, ambíguo)
fn mais_longo<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// Sem anotação possível: o compilador daria erro
// fn ambiguo(x: &str, y: &str) -> &str { x } // ERRO!
Newtype Pattern
O que é: Padrão de design que consiste em envolver um tipo existente em uma tuple struct com um único campo, criando um novo tipo distinto. Permite implementar traits externos para tipos externos (contornando a orphan rule), adicionar semântica e validação, e evitar confusão entre valores do mesmo tipo subjacente.
Exemplo:
// Sem newtype: fácil confundir parâmetros
fn criar_usuario_ruim(nome: String, email: String, senha: String) {}
// Com newtype: tipos distintos impedem confusão
struct Nome(String);
struct Email(String);
struct Senha(String);
fn criar_usuario(nome: Nome, email: Email, senha: Senha) {
// impossível passar email onde espera nome
}
// Validação no construtor
impl Email {
fn novo(valor: &str) -> Result<Self, String> {
if valor.contains('@') {
Ok(Email(valor.to_string()))
} else {
Err("Email inválido".into())
}
}
}
// Implementar trait externo para tipo externo
struct Milissegundos(u64);
impl std::fmt::Display for Milissegundos {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0 >= 1000 {
write!(f, "{:.1}s", self.0 as f64 / 1000.0)
} else {
write!(f, "{}ms", self.0)
}
}
}
fn main() {
let email = Email::novo("user@exemplo.com").unwrap();
let tempo = Milissegundos(2500);
println!("{}", tempo); // "2.5s"
}
PhantomData
O que é: Tipo marcador de tamanho zero (std::marker::PhantomData<T>) que indica ao compilador que uma struct “possui” logicamente um tipo T, mesmo sem armazená-lo diretamente. Usado para marcar relações de lifetime, variância de tipos genéricos e ownership lógica sem custo em runtime.
Exemplo:
use std::marker::PhantomData;
// Struct com parâmetro de tipo que não é usado diretamente
struct Canal<T> {
id: u32,
_tipo: PhantomData<T>, // marca que o canal é "para" mensagens de tipo T
}
struct Texto;
struct Binario;
impl<T> Canal<T> {
fn novo(id: u32) -> Self {
Canal { id, _tipo: PhantomData }
}
}
// Funções específicas por tipo
impl Canal<Texto> {
fn enviar(&self, msg: &str) {
println!("Canal {}: enviando texto '{}'", self.id, msg);
}
}
impl Canal<Binario> {
fn enviar(&self, dados: &[u8]) {
println!("Canal {}: enviando {} bytes", self.id, dados.len());
}
}
fn main() {
let texto: Canal<Texto> = Canal::novo(1);
let binario: Canal<Binario> = Canal::novo(2);
texto.enviar("olá");
binario.enviar(&[0x48, 0x65, 0x6C]);
// texto.enviar(&[1, 2, 3]); // ERRO: tipo incompatível
}
Trait Bound
O que é: Restrição aplicada a um parâmetro de tipo genérico que exige que o tipo implemente um ou mais traits. Pode ser escrito diretamente na declaração do genérico (T: Display + Clone) ou em uma cláusula where. Garante que o código genérico só será usado com tipos que fornecem o comportamento necessário.
Exemplo:
use std::fmt::{Display, Debug};
// Bound direto
fn imprimir<T: Display>(valor: T) {
println!("{}", valor);
}
// Múltiplos bounds
fn depurar<T: Display + Debug + Clone>(valor: T) {
let copia = valor.clone();
println!("Display: {} | Debug: {:?}", valor, copia);
}
// Cláusula where (mais legível para múltiplos parâmetros)
fn processar<T, U>(chave: T, valor: U) -> String
where
T: Display + Ord,
U: Debug + Default,
{
format!("{}: {:?}", chave, valor)
}
// Bound em struct
struct Registro<T: Display> {
dados: T,
}
// Bound condicional em impl
struct Wrapper<T>(T);
impl<T: Display> Wrapper<T> {
fn exibir(&self) {
println!("{}", self.0);
}
}
// Sem o bound, exibir() não está disponível:
// let w = Wrapper(vec![1, 2, 3]);
// w.exibir(); // ERRO: Vec<i32> não implementa Display
Type Alias
O que é: Cria um nome alternativo para um tipo existente usando a palavra-chave type. Nao cria um tipo novo (diferente do newtype pattern) – é apenas um sinônimo. Útil para simplificar tipos complexos, especialmente Results com tipos de erro específicos.
Exemplo:
// Simplificar tipos longos
type Resultado<T> = Result<T, Box<dyn std::error::Error>>;
type Callback = Box<dyn Fn(i32) -> i32>;
type Mapa = std::collections::HashMap<String, Vec<i32>>;
fn processar(dados: &str) -> Resultado<i32> {
let numero: i32 = dados.parse()?;
Ok(numero * 2)
}
fn aplicar(callback: Callback, valor: i32) -> i32 {
callback(valor)
}
// Comum em bibliotecas de IO
type IoResult<T> = Result<T, std::io::Error>;
fn ler_arquivo(caminho: &str) -> IoResult<String> {
std::fs::read_to_string(caminho)
}
fn main() {
let cb: Callback = Box::new(|x| x * 3);
println!("{}", aplicar(cb, 10)); // 30
}
where Clause
O que é: Sintaxe alternativa para especificar trait bounds em funções, structs e implementações genéricas. Colocada após a assinatura/declaração, a cláusula where é mais legível quando há muitos parâmetros genéricos ou bounds complexos, e é a única forma de expressar certos bounds avançados.
Exemplo:
use std::fmt::{Display, Debug};
use std::hash::Hash;
// Sem where (fica longo e difícil de ler)
fn processar_inline<T: Display + Debug + Clone + Hash, U: Display + Default>(t: T, u: U) {}
// Com where (muito mais legível)
fn processar<T, U>(t: T, u: U)
where
T: Display + Debug + Clone + Hash,
U: Display + Default,
{
println!("{}: {}", t, u);
}
// where permite bounds que não são possíveis inline
fn imprimir_se_debug<T>(valor: T)
where
T: Debug,
Vec<T>: Debug, // bound em tipo derivado
{
let v = vec![valor];
println!("{:?}", v);
}
// where em impl
struct Cache<K, V> {
dados: std::collections::HashMap<K, V>,
}
impl<K, V> Cache<K, V>
where
K: Eq + Hash + Display,
V: Clone + Debug,
{
fn obter(&self, chave: &K) -> Option<V> {
self.dados.get(chave).cloned()
}
}
Associated Type (Tipo Associado)
O que é: Um tipo placeholder dentro de um trait que deve ser especificado na implementação. Diferente de parâmetros genéricos, tipos associados permitem apenas uma implementação por tipo. O exemplo mais conhecido é type Item no trait Iterator. Simplifica a sintaxe e evita ambiguidade.
Exemplo:
// Trait com tipo associado
trait Conversor {
type Saida;
type Erro;
fn converter(&self) -> Result<Self::Saida, Self::Erro>;
}
struct TextoParaNumero(String);
impl Conversor for TextoParaNumero {
type Saida = i32;
type Erro = std::num::ParseIntError;
fn converter(&self) -> Result<i32, std::num::ParseIntError> {
self.0.parse()
}
}
// Comparação: Iterator usa tipo associado
// trait Iterator {
// type Item; // tipo associado, não genérico
// fn next(&mut self) -> Option<Self::Item>;
// }
// Se fosse genérico, seria possível implementar Iterator<i32> E Iterator<String>
// para o mesmo tipo, o que geraria ambiguidade em chamadas como .next()
fn main() {
let conv = TextoParaNumero("42".into());
match conv.converter() {
Ok(n) => println!("Convertido: {}", n),
Err(e) => println!("Erro: {}", e),
}
}
Channel (Canal)
O que é: Primitiva de comunicação entre threads baseada no paradigma de passagem de mensagens. Rust oferece std::sync::mpsc (multiple producer, single consumer) na biblioteca padrão. Bibliotecas como tokio e crossbeam oferecem canais adicionais, incluindo canais assíncronos e multi-consumer.
Exemplo:
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// Criar canal (tx = transmissor, rx = receptor)
let (tx, rx) = mpsc::channel();
// Clonar transmissor para múltiplos produtores
let tx2 = tx.clone();
// Produtor 1
thread::spawn(move || {
for i in 0..3 {
tx.send(format!("Prod1: mensagem {}", i)).unwrap();
thread::sleep(Duration::from_millis(100));
}
});
// Produtor 2
thread::spawn(move || {
for i in 0..3 {
tx2.send(format!("Prod2: mensagem {}", i)).unwrap();
thread::sleep(Duration::from_millis(150));
}
});
// Consumidor (recebe de ambos os produtores)
for msg in rx {
println!("Recebido: {}", msg);
}
println!("Todos os transmissores foram fechados");
}
RwLock
O que é: Primitiva de sincronização que permite múltiplos leitores simultâneos ou um único escritor exclusivo. Diferente do Mutex, que permite apenas um acesso por vez, o RwLock<T> otimiza cenários onde leituras são muito mais frequentes que escritas.
Exemplo:
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let dados = Arc::new(RwLock::new(vec![1, 2, 3]));
let mut handles = vec![];
// Múltiplos leitores simultâneos
for i in 0..3 {
let dados = Arc::clone(&dados);
handles.push(thread::spawn(move || {
let leitura = dados.read().unwrap();
println!("Leitor {}: {:?}", i, *leitura);
}));
}
// Um escritor exclusivo
{
let dados = Arc::clone(&dados);
handles.push(thread::spawn(move || {
let mut escrita = dados.write().unwrap();
escrita.push(4);
println!("Escritor: adicionou 4");
}));
}
for h in handles {
h.join().unwrap();
}
println!("Final: {:?}", *dados.read().unwrap());
}
Cow (Clone on Write)
O que é: Enum inteligente (std::borrow::Cow) que pode conter dados emprestados (Borrowed) ou dados próprios (Owned). Útil para funções que geralmente retornam dados sem modificação (evitando cópia), mas ocasionalmente precisam criar novos dados. Implementa Deref para acesso transparente.
Exemplo:
use std::borrow::Cow;
fn normalizar_texto(input: &str) -> Cow<str> {
if input.chars().all(|c| c.is_lowercase() || !c.is_alphabetic()) {
// Já normalizado: retorna referência (sem cópia)
Cow::Borrowed(input)
} else {
// Precisa converter: cria nova String
Cow::Owned(input.to_lowercase())
}
}
fn main() {
let texto1 = "já está ok";
let texto2 = "PRECISA Converter";
let r1 = normalizar_texto(texto1); // Borrowed (zero allocation)
let r2 = normalizar_texto(texto2); // Owned (alocou nova String)
println!("{}", r1); // "já está ok"
println!("{}", r2); // "precisa converter"
// Pode usar como &str em ambos os casos
fn aceita_str(s: &str) { println!("{}", s); }
aceita_str(&r1);
aceita_str(&r2);
}