Skip to content

Rust Folha de referência

Linguagem de sistemas focada em segurança, velocidade e concorrência.

01

Básico

Variáveis & Mutabilidade

Variáveis em Rust são imutáveis por padrão — esse é um recurso de segurança core que previne mutação acidental. Use 'mut' apenas quando genuinamente precisar mudar um valor. 'const' requer tipo explícito e é inlined em tempo de compilação, enquanto 'static' tem um endereço de memória fixo com lifetime 'static.

rust
let x = 5;            // immutable by default
let mut y = 10;        // mutable with 'mut'
y += 1;                // OK, y is mutable

const MAX: u32 = 100;  // compile-time constant
static GREETING: &str = "Hi"; // global static

let z: i32 = -5;       // explicit type annotation

Shadowing

Shadowing permite reusar um nome de variável enquanto muda seu tipo ou valor. Diferente de 'mut', shadowing cria um novo binding — útil para transformar dados (ex.: fazer parse de string para int) sem inventar um novo nome. O valor antigo é dropped após o novo binding.

rust
let x = 5;
let x = x * 2;         // shadow previous x, now 10
let x = "text";        // can even change type!
println!("{}", x);     // "text"

// shadowing vs mut: shadowing creates a NEW variable
// with the same name, allowing type changes

Comentários & Impressão

Use /// para documentação de item (mostrada por 'cargo doc') e //! para docs de módulo/crate. println! escreve para stdout, eprintln! para stderr. O placeholder {} suporta especificações de formatação: > para alinhar à direita, < para alinhar à esquerda, ^ para centro, .N para precisão, #b/#o/#x para binário/octal/hex.

rust
// Line comment
/* Block comment */
/// Doc comment (renders in rustdoc)
//! Module-level doc comment

let name = "Alice";
println!("Hello, {}!", name);        // format string
println!("{0} {1} {0}", "a", "b");   // positional args
println!("{:>5}", 42);               // right-align, width 5
println!("{:.2}", 3.14159);          // 2 decimal places: 3.14
println!("{:#b}", 0b1010);           // binary: 0b1010
eprintln!("Error to stderr");        // error output

Visão Geral de Tipos de Dados

Rust não tem conversão de tipo implícita — use 'as' para casts. i32 é o tipo inteiro padrão, f64 o float padrão. usize é usado para indexação e tamanhos. char é um valor escalar Unicode completo (não um byte), então '🦀' é um único char.

rust
// Scalar types
let a: i32 = 42;          // signed 32-bit integer
let b: u64 = 100;         // unsigned 64-bit
let c: f64 = 3.14;        // 64-bit float (default)
let d: bool = true;
let e: char = '🦀';        // 4-byte Unicode scalar

// isize/usize: pointer-sized (32 or 64 bit depending on arch)
let len: usize = vec![1,2,3].len();

// Compound types
let pair: (i32, &str) = (1, "hello");
let arr: [i32; 3] = [0; 3];   // [0, 0, 0]

Type Casting & Aliases

O cast 'as' é unchecked e pode perder dados (ex.: 300u16 as u8 vira 44). Para conversões seguras use traits From/Into que são implementadas para casts sem perda. Type aliases melhoram legibilidade sem criar novos tipos — use 'struct NewType(i32)' para um padrão newtype distinto.

rust
// 'as' keyword for primitive casts (may truncate)
let x: i32 = 42;
let y: f64 = x as f64;        // 42.0
let z: u8 = 300u16 as u8;     // truncated: 44

// From/Into traits for safe conversion
let s = String::from("hi");
let owned: String = "hi".into();

// Type aliases
type Kilometers = i32;
let distance: Kilometers = 5;
02

Strings

String vs &str

A distinção entre String (owned, heap) e &str (borrowed slice) é fundamental em Rust. Use String quando precisar possuir/modificar/crescer o texto; use &str quando apenas precisa lê-lo. &str pode apontar tanto para o buffer de uma String quanto para um string literal no binário. Prefira &str em parâmetros de função para flexibilidade.

rust
// &str: immutable string slice (borrowed)
let s1: &str = "literal";           // stored in binary
let s2: &str = &s3[..2];            // slice of a String

// String: owned, growable, heap-allocated
let mut s3: String = String::from("hello");
s3.push_str(", world");             // append
s3.push('!');                       // append char
s3.insert(0, '>');                  // insert at index
println!("{}", s3);                 // >hello, world!

// Convert: &str -> String
let owned = "literal".to_string();
let owned2 = String::from("literal");

Métodos de String

Métodos de String retornam novas Strings owned em vez de modificar in place (exceto métodos push_*/insert em Strings mutáveis). split() retorna um iterador, então use .collect() para materializar. Note que indexar s[0] NÃO é permitido em strings porque boundaries UTF-8 não se alinham com índices de byte — use s.chars().nth(0) em vez disso.

rust
let s = String::from("Hello, World");

// inspection
println!("len: {}", s.len());              // 12
println!("is_empty: {}", s.is_empty());    // false
println!("contains 'World': {}", s.contains("World")); // true
println!("starts_with 'Hello': {}", s.starts_with("Hello")); // true

// transformation
let upper = s.to_uppercase();              // HELLO, WORLD
let replaced = s.replace("o", "0");        // Hell0, W0rld
let trimmed = "  hi  ".trim().to_string(); // hi

// splitting
for word in s.split(", ") {
    println!("{}", word); // Hello / World
}
let parts: Vec<&str> = s.split_whitespace().collect();

Formatação & Concatenação

O operador + toma ownership da String esquerda e pega emprestado a direita (&str). É por isso que s1 se torna inválido após s1 + &s2. Para encadear múltiplas strings, prefira format! que é mais legível e não move nenhum operando. concat! funciona apenas em literals e produz um &'static str.

rust
// format! macro creates a new String
let name = "Alice";
let msg = format!("Hi {}, you have {} messages", name, 5);

// concatenation
let s1 = String::from("Hello");
let s2 = String::from(" World");
let s3 = s1 + &s2;        // s1 moved, s2 borrowed
// s1 is now invalid!

let s4 = format!("{}{}", s2, s3); // neither moved

// concat! macro for literals
let s5 = concat!("foo", "bar"); // "foobar" at compile time

Iterando Caracteres & Bytes

Strings em Rust são codificadas em UTF-8, então índice de byte != índice de char. chars() itera valores escalares Unicode (O(n) para decodificar), bytes() itera bytes raw. Slicing com [n..m] entra em panic se n ou m cair no meio de um caractere multi-byte. Para acesso em nível de byte, converta para Vec<u8> via as_bytes().

rust
let s = "héllo";  // é is 2 bytes in UTF-8

// iterate over chars (Unicode scalar values)
for c in s.chars() {
    print!("{} ", c);  // h é l l o
}

// iterate over bytes
for b in s.bytes() {
    print!("{} ", b);  // 104 195 169 108 108 111
}

// get char at index (O(n) — must walk UTF-8)
let third = s.chars().nth(2); // Some('l')

// string slices must be on char boundaries
let slice = &s[0..1]; // "h" — OK
// let bad = &s[0..2]; // PANIC if mid-é!

Parsing & Conversão

parse() retorna Result porque a string pode não ser um número válido — sempre trate o erro. A sintaxe turbofish parse::<T>() permite especificar o tipo inline. Converter String para &str é grátis (apenas um borrow), mas &str para String aloca. collect() pode construir uma String a partir de um iterador de chars.

rust
// String/str -> number
let n: i32 = "42".parse().unwrap();
let n2 = "42".parse::<i32>().unwrap(); // turbofish syntax
let f: f64 = "3.14".parse().unwrap();

// number -> String
let s = 42.to_string();
let s2 = format!("{}", 42);

// String -> &str (free, just dereference)
let owned = String::from("hi");
let borrowed: &str = &owned;

// collect chars into String
let upper: String = "hello".chars().map(|c| c.to_uppercase().next().unwrap()).collect();
03

Estruturas de Dados

Arrays & Slices

Arrays [T; N] têm um tamanho fixo conhecido em tempo de compilação e vivem na stack. Slices &[T] são fat pointers (pointer + length) que pegam emprestado uma sequência contígua — eles permitem que funções aceitem qualquer array ou vector sem se importar com o tamanho. Use slices em assinaturas de função para generalidade.

rust
// Fixed-size array (stack allocated)
let arr: [i32; 3] = [1, 2, 3];
let zeros = [0; 5];           // [0, 0, 0, 0, 0]
println!("first: {}", arr[0]);
println!("len: {}", arr.len());

// Slice: a view into an array/vector
let slice: &[i32] = &arr[1..3];  // [2, 3]
let full: &[i32] = &arr;          // whole array
let first = &arr[..1];            // [1]

// iterating
for n in &arr {
    println!("{}", n);
}

Vectors (Vec<T>)

Vec<T> é o array cresível do Rust, backed por memória heap com capacidade dobrando. push/pop são O(1) amortizado; insert/remove são O(n) porque elementos shift. Use .get(i) em vez de v[i] quando quiser acesso seguro (retorna Option). into_iter() consome o vector, produzindo valores owned.

rust
// growable heap array
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3];          // macro shorthand

v.push(4);                        // append
v.pop();                          // remove last -> Option
v.insert(0, 0);                   // insert at index (O(n))
v.remove(0);                      // remove at index (O(n))
v.extend([5, 6]);                 // append multiple

// access
println!("{}", v[0]);             // panics if out of bounds
println!("{:?}", v.get(0));       // Some(&4) — safe

// iterate by value, ref, or mut ref
for n in &v { print!("{}", n); }
for n in &mut v { *n *= 2; }      // double each
let owned: Vec<i32> = v.into_iter().collect();

HashMap & BTreeMap

HashMap usa hashing para acesso O(1) médio, mas não tem ordenação. BTreeMap usa uma B-tree para acesso O(log n), mas mantém chaves ordenadas. Use HashMap quando precisar de lookups rápidos; use BTreeMap quando precisar de iteração ordenada ou range queries. entry().or_insert() é a forma idiomática de 'upsert' — retorna uma referência mutável ao valor, inserindo um default se ausente.

rust
use std::collections::HashMap;
use std::collections::BTreeMap;

// HashMap: O(1) average lookup, unordered
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Alice"), 10);
scores.entry("Bob".into()).or_insert(0); // insert if absent
let alice = scores.get("Alice"); // Some(&10)

// iterate (unordered)
for (name, score) in &scores {
    println!("{}: {}", name, score);
}

// BTreeMap: O(log n) lookup, sorted by key
let mut bt: BTreeMap<String, i32> = BTreeMap::new();
bt.insert("zebra".into(), 1);
bt.insert("apple".into(), 2);
// iterates in sorted order: apple, zebra

HashSet & BTreeSet

HashSet armazena valores únicos com verificações de pertinência O(1). Operações de set (union, intersection, difference, symmetric_difference) retornam iteradores. Use sets para desduplicação, teste de pertinência e operações matemáticas de set. BTreeSet é o equivalente ordenado backed por BTreeMap.

rust
use std::collections::HashSet;

let mut a: HashSet<i32> = [1, 2, 3].into_iter().collect();
let b: HashSet<i32> = [3, 4, 5].into_iter().collect();

a.insert(4);
a.remove(&1);
println!("contains 2: {}", a.contains(&2));

// set operations
let union: HashSet<_> = a.union(&b).copied().collect();
let inter: HashSet<_> = a.intersection(&b).copied().collect();
let diff: HashSet<_> = a.difference(&b).copied().collect();
let sym: HashSet<_> = a.symmetric_difference(&b).copied().collect();

Tuples & Destructuring

Tuples agrupam um número fixo de valores de tipos potencialmente diferentes. Acesse campos com .0, .1, etc. Destructuring com let patterns é idiomático. O unit type () tem um valor () e é usado como void em outras linguagens. Tuples são comumente usados para retornar múltiplos valores de funções.

rust
// tuples can hold different types
let tup: (i32, f64, &str) = (42, 3.14, "hi");

// access by index
println!("{} {} {}", tup.0, tup.1, tup.2);

// destructuring
let (n, pi, s) = tup;
println!("{} {} {}", n, pi, s);

// nested
let ((a, b), c) = ((1, 2), 3);

// unit tuple (empty)
let unit: () = ();

// function returning multiple values
fn divmod(a: i32, b: i32) -> (i32, i32) {
    (a / b, a % b)
}
let (q, r) = divmod(17, 5); // (3, 2)
04

Fluxo de Controle

If / Else If / Else

Diferente de muitas linguagens, 'if' em Rust é uma expressão que retorna um valor. Isso elimina a necessidade de um operador ternário (condition ? a : b) — apenas use if/else. Ambos os branches devem retornar o mesmo tipo. A condição NÃO precisa de parênteses, mas o body deve ser um block { }.

rust
let score = 85;

// if is an expression — returns a value
let grade = if score >= 90 {
    "A"
} else if score >= 80 {
    "B"
} else if score >= 70 {
    "C"
} else {
    "F"
};
println!("Grade: {}", grade);

// arms must return same type
// let bad = if true { 1 } else { "two" }; // ERROR!

Loops: loop, while, for

loop cria um loop infinito — break sai e pode retornar um valor (break value). while verifica uma condição antes de cada iteração. for é o loop mais comum, iterando sobre ranges, arrays, vectors, iteradores. Use 'continue' para pular para a próxima iteração. Ranges: a..b é exclusivo, a..=b é inclusivo.

rust
// loop: infinite, use break to exit
let mut count = 0;
let result = loop {
    count += 1;
    if count == 10 {
        break count * 2; // break with value
    }
};
println!("{}", result); // 20

// while: condition-checked loop
let mut n = 5;
while n > 0 {
    println!("{}", n);
    n -= 1;
}

// for: iterate over anything with IntoIterator
for i in 0..5 { print!("{}", i); }      // 01234
for i in (1..=3).rev() { print!("{}", i); } // 321
for ch in "hello".chars() { print!("{}", ch); }

Match (Pattern Matching)

match é a poderosa construção de pattern matching do Rust. Deve ser exaustivo (todas as possibilidades cobertas) — use _ como catch-all. Padrões suportam literals, ranges (..=), or-patterns (|), bindings e guards (if). match é uma expressão e retorna um valor. É a forma idiomática de lidar com enums como Option e Result.

rust
let coin = 25;
match coin {
    25 => println!("quarter"),
    10 => println!("dime"),
    5 => println!("nickel"),
    1 => println!("penny"),
    _ => println!("unknown"),  // catch-all (required)
}

// matching with bindings
let x = 3;
match x {
    1 | 2 => println!("one or two"),   // or-pattern
    3..=9 => println!("single digit"),  // range
    n if n % 2 == 0 => println!("even: {}", n), // guard
    _ => println!("other"),
}

// match is exhaustive — all cases must be covered

If Let & While Let

if let é açúcar sintático para match quando você só se importa com uma variante. É menos verboso, mas menos exaustivo que match — use-o quando os outros cases não importam. while let faz loop enquanto o padrão corresponde, comumente usado com iteradores (next() retorna Option). O branch else é opcional.

rust
// if let: shorthand for matching one pattern
let maybe: Option<i32> = Some(5);

// verbose: match
match maybe {
    Some(x) => println!("got {}", x),
    None => println!("nothing"),
}

// concise: if let
if let Some(x) = maybe {
    println!("got {}", x);
} else {
    println!("nothing");
}

// while let: loop while pattern matches
let mut iter = vec![1, 2, 3].into_iter();
while let Some(n) = iter.next() {
    println!("{}", n);
}

Break, Continue & Labels

Labels (single quote + name) permitem break ou continue em loops externos de dentro de loops aninhados. Isso é essencial quando você precisa sair de múltiplos níveis de loop de uma vez. Sem labels, break/continue afetam apenas o loop mais interno. Labels são uma alternativa limpa a variáveis de flag.

rust
// continue: skip to next iteration
for i in 0..10 {
    if i % 2 == 0 { continue; }
    println!("{}", i); // prints odd numbers
}

// break: exit loop
for i in 0..10 {
    if i == 5 { break; }
    println!("{}", i); // prints 0..4
}

// labeled loops (for nested break/continue)
'outer: for i in 0..3 {
    for j in 0..3 {
        if i == 1 && j == 1 {
            break 'outer; // breaks the outer loop
        }
        println!("{} {}", i, j);
    }
}
05

Funções & Closures

Definindo Funções

Funções usam a keyword 'fn'. A última expressão sem ponto e vírgula é o valor de retorno (expression). Adicionar um ponto e vírgula a torna um statement retornando (). Use 'return' explícito apenas para saídas antecipadas. O tipo de retorno ! marca diverging functions que nunca retornam (loops infinitos, panics, exits de processo).

rust
// basic function with return type
fn add(a: i32, b: i32) -> i32 {
    a + b  // no semicolon = expression = return value
}

// statements (with semicolon) return ()
fn greet(name: &str) {
    println!("Hi, {}", name);
    // implicit return ()
}

// explicit return
fn abs(x: i32) -> i32 {
    if x < 0 {
        return -x;  // early return needs 'return'
    }
    x  // tail expression
}

// diverging function (never returns)
fn forever() -> ! {
    loop {}
}

Parâmetros & Argumentos

Rust não tem function overloading ou parâmetros opcionais (use generics ou builders em vez disso). Escolha tipos de parâmetro cuidadosamente: &T para acesso de leitura, &mut T para acesso de escrita, T para transferência de ownership. Slices (&[T]) são a forma idiomática de aceitar sequências de comprimento variável. Argumentos padrão não são suportados — use o padrão builder ou Option<T>.

rust
// immutable borrow
fn len(s: &String) -> usize { s.len() }

// mutable borrow
fn push(v: &mut Vec<i32>) { v.push(42); }

// take ownership
fn consume(s: String) { println!("{}", s); }

// multiple return via tuple
fn swap(a: i32, b: i32) -> (i32, i32) { (b, a) }

// variadic-ish via slices
fn sum(nums: &[i32]) -> i32 {
    nums.iter().sum()
}
println!("{}", sum(&[1, 2, 3, 4])); // 10

Closures

Closures são funções anônimas que podem capturar seu ambiente. Elas são inferidas pelo uso. Closures capturam por referência por padrão; 'move' força transferência de ownership (essencial para threads). Closures implementam traits Fn (borrow), FnMut (mut borrow) ou FnOnce (consume), habilitando-as a serem passadas como parâmetros de função.

rust
// closure syntax: |params| body
let add = |a, b| a + b;
println!("{}", add(1, 2)); // 3

// type annotations (rarely needed)
let square = |x: i32| -> i32 { x * x };

// capturing environment
let multiplier = 3;
let multiply = |x| x * multiplier; // borrows multiplier
println!("{}", multiply(5)); // 15

// move closure: takes ownership of captured vars
let name = String::from("Alice");
let greet = move || println!("Hi {}", name);
// name is now moved into greet
greet();

Higher-Order Functions & Iteradores

Iteradores em Rust são preguiçosos — operações não executam até .collect() ou outro método consumidor ser chamado. Isso permite abstração de zero-cost: o compilador pode otimizar métodos de iterador encadeados em loops eficientes. Métodos comuns: map (transformar), filter (selecionar), fold (acumular), take (limitar), skip, enumerate, zip, flat_map.

rust
let nums = vec![1, 2, 3, 4, 5];

// map: transform each element
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();

// filter: keep elements matching predicate
let evens: Vec<&i32> = nums.iter().filter(|&&x| x % 2 == 0).collect();

// fold/reduce: accumulate
let sum: i32 = nums.iter().sum();              // 15
let product: i32 = nums.iter().product();       // 120
let combined = nums.iter().fold(0, |acc, x| acc + x);

// chain multiple operations (lazy!)
let result: Vec<i32> = nums.iter()
    .filter(|&&x| x > 1)
    .map(|&x| x * x)
    .collect(); // [4, 9, 16, 25]

Function Pointers & Traits

fn (lowercase) é um tipo de function pointer — zero-cost, mas não pode capturar ambiente. Para closures que capturam, use bounds genéricos <F: Fn(...)>. Fn pega emprestado, FnMut pega emprestado mutavelmente, FnOnce consome. Function pointers são úteis para armazenar funções em structs ou passar para C. Generics com bounds Fn são mais flexíveis e ainda zero-cost quando monomorphized.

rust
// function pointer type
type MathFn = fn(i32, i32) -> i32;

fn add(a: i32, b: i32) -> i32 { a + b }
fn mul(a: i32, b: i32) -> i32 { a * b }

fn apply(f: MathFn, a: i32, b: i32) -> i32 {
    f(a, b)
}
println!("{}", apply(add, 3, 4)); // 7
println!("{}", apply(mul, 3, 4)); // 12

// generic over Fn trait (accepts closures too)
fn apply_fn<F: Fn(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
    f(a, b)
}
let closure = |a, b| a - b;
println!("{}", apply_fn(closure, 10, 3)); // 7
06

Ownership & Borrowing

Regras de Ownership

Ownership é o sistema core de gerenciamento de memória do Rust — sem garbage collector necessário. Quando você atribui um valor heap (String, Vec), ownership MOVE e a variável antiga se torna inválida. Tipos de stack (i32, f64, bool, char, tuples de tipos Copy) implementam Copy e são duplicados em vez disso. Isso elimina bugs use-after-free e double-free em tempo de compilação.

rust
// Rule 1: Each value has ONE owner
let s1 = String::from("hello");
let s2 = s1;  // s1's ownership MOVED to s2
// println!("{}", s1); // ERROR: s1 is invalid after move

// Rule 2: When owner goes out of scope, value is dropped
{
    let s = String::from("temp");
    // s is valid here
} // s is automatically dropped (memory freed)

// Rule 3: Copy types (i32, bool, char, etc.) are copied, not moved
let a = 5;
let b = a;  // a is copied, both valid
println!("{} {}", a, b); // OK

Borrowing & Referências

Borrowing permite usar um valor sem tomar ownership. &T cria uma referência imutável — você pode ter muitas simultaneamente. &mut T cria uma referência mutável — mas apenas UMA referência mutável OU qualquer número de referências imutáveis, nunca ambas. Isso previne data races em tempo de compilação. Referências devem sempre apontar para dados válidos (sem dangling pointers).

rust
// &T: immutable borrow (read-only, multiple allowed)
fn calc_len(s: &String) -> usize {
    s.len()
    // s goes out of scope but is NOT dropped (we don't own it)
}
let s = String::from("hello");
let len = calc_len(&s);  // borrow s, don't move it
println!("'{}' has length {}", s, len); // s still valid

// &mut T: mutable borrow (exclusive, only ONE at a time)
fn push_world(s: &mut String) {
    s.push_str(", world");
}
let mut s2 = String::from("hello");
push_world(&mut s2);
println!("{}", s2); // hello, world

Slice References

Slices são referências a uma porção contígua de uma coleção. Elas são 'fat pointers' contendo um pointer e length. String slices (&str) permitem que funções aceitem tanto String quanto string literals. Array slices (&[T]) funcionam com qualquer sequência contígua. Slices pegam emprestado os dados subjacentes, impedindo-os de serem modificados ou dropped enquanto o slice existe.

rust
// string slice: &str
let s = String::from("hello world");
let hello: &str = &s[0..5];   // "hello"
let world: &str = &s[6..];    // "world"
let full: &str = &s[..];      // "hello world"

// array slice: &[T]
let arr = [1, 2, 3, 4, 5];
let mid: &[i32] = &arr[1..4]; // [2, 3, 4]

// function accepting slices (idiomatic)
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

Lifetimes

Lifetimes dizem ao compilador por quanto tempo referências são válidas. A anotação 'a não muda lifetimes — descreve relacionamentos. 'static é um lifetime especial durando todo o programa (string literals o têm). A maioria do código usa lifetime elision (compilador infere). Você precisa de lifetimes explícitas quando: uma função retorna uma referência, ou um struct mantém uma referência.

rust
// explicit lifetime annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
// 'a means: the returned reference lives as long as
// the SHORTEST of x and y's lifetimes

let s1 = String::from("long string");
let s2 = String::from("hi");
let result = longest(s1.as_str(), s2.as_str());
println!("Longest: {}", result);

// struct holding references needs lifetime
struct Excerpt<'a> {
    part: &'a str,
}
let novel = String::from("call me Ishmael. years ago...");
let first_sentence = novel.split('.').next().unwrap();
let ex = Excerpt { part: first_sentence };

Smart Pointers

Box<T> move dados para o heap (proprietário único). Rc<T> habilita ownership compartilhada via reference counting (apenas single-threaded). Arc<T> é a versão thread-safe usando atomics. RefCell<T> move borrow checking para runtime, permitindo mutação através de referências compartilhadas (interior mutability). Use Box para tipos recursivos, Rc/Arc para estruturas tipo grafo, RefCell quando precisa mutar dados compartilhados.

rust
use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;

// Box<T>: heap allocation, single owner
let b = Box::new(5); // 5 lives on heap
println!("{}", b);   // dereferenced automatically

// Rc<T>: reference counting, multiple owners (single-threaded)
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a); // increments ref count
println!("count: {}", Rc::strong_count(&a)); // 2

// Arc<T>: atomic Rc, thread-safe
let arc = Arc::new(vec![1, 2, 3]);

// RefCell<T>: interior mutability (runtime borrow check)
let cell = RefCell::new(5);
*cell.borrow_mut() += 1; // mutable borrow checked at runtime
07

Structs, Enums & Traits

Definindo Structs

Structs agrupam campos relacionados. Named-field structs são mais comuns. Tuple structs são úteis quando nomes de campo não são significativos (Color, Point). Unit structs não têm dados e são usados para implementar traits. A sintaxe .. copia campos não especificados de outra instância. Structs são stack-allocated a menos que contenham tipos heap (String, Vec, Box).

rust
// named-field struct
struct User {
    name: String,
    age: u32,
    active: bool,
}

let u = User {
    name: String::from("Alice"),
    age: 30,
    active: true,
};

// field init shorthand
let name = String::from("Bob");
let u2 = User { name, age: 25, active: true };

// update syntax (copy remaining fields from another)
let u3 = User { age: 40, ..u2 };

// tuple struct
struct Color(u8, u8, u8);
let red = Color(255, 0, 0);

// unit struct (no fields, useful for traits)
struct AlwaysEqual;

Métodos com impl

Métodos vão em blocks impl. &self pega emprestado imutavelmente, &mut self pega emprestado mutavelmente, self toma ownership (consuming). Associated functions (sem parâmetro self) são como static methods — chamadas com Type::function(). Self é um alias para o tipo. Múltiplos blocks impl são permitidos, úteis para dividir métodos por concern ou compilação condicional.

rust
struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    // associated function (constructor, no &self)
    fn new(w: f64, h: f64) -> Self {
        Rectangle { width: w, height: h }
    }

    // method (borrows self)
    fn area(&self) -> f64 {
        self.width * self.height
    }

    // mutable method
    fn scale(&mut self, factor: f64) {
        self.width *= factor;
        self.height *= factor;
    }

    // consuming method (takes ownership)
    fn into_square(self) -> Rectangle {
        let side = (self.width + self.height) / 2.0;
        Rectangle { width: side, height: side }
    }
}

let mut r = Rectangle::new(10.0, 5.0);
println!("Area: {}", r.area()); // 50
r.scale(2.0);
let sq = r.into_square(); // r consumed

Enums & Pattern Matching

Enums em Rust são algebraic data types — cada variante pode carregar dados diferentes. Isso os torna muito mais poderosos que enums C. Pattern matching com match destructures variantes e extrai seus dados. Use enums quando um valor pode ser uma de várias formas distintas. A macro matches! é um shorthand para matching de padrão único retornando bool.

rust
// enum with data (algebraic data type)
enum Message {
    Quit,                          // no data
    Move { x: i32, y: i32 },       // named fields
    Write(String),                 // tuple variant
    ChangeColor(i32, i32, i32),    // tuple variant
}

// pattern matching with destructuring
fn process(msg: Message) {
    match msg {
        Message::Quit => println!("Quit"),
        Message::Move { x, y } => println!("Move to ({}, {})", x, y),
        Message::Write(text) => println!("Write: {}", text),
        Message::ChangeColor(r, g, b) => println!("RGB({}, {}, {})", r, g, b),
    }
}

process(Message::Move { x: 10, y: 20 });
process(Message::Write(String::from("hello")));

// enums can have methods too
impl Message {
    fn is_quit(&self) -> bool {
        matches!(self, Message::Quit)
    }
}

Option & Result

Option<T> substitui null — você deve tratar explicitamente o case None, eliminando bugs estilo NullPointerException. Result<T, E> é para operações que podem falhar. Ambos têm métodos ricos: map (transformar), and_then (encadear), unwrap_or (default), is_some/is_ok (verificar). O operador ? em Result propaga erros automaticamente. Esses dois tipos são a espinha dorsal do tratamento de erros do Rust.

rust
// Option<T>: Some(value) or None (replaces null)
fn find_user(id: i32) -> Option<String> {
    if id == 1 { Some(String::from("Alice")) }
    else { None }
}

let user = find_user(1);
match user {
    Some(name) => println!("Found: {}", name),
    None => println!("Not found"),
}

// convenient methods
let name = find_user(1).unwrap_or("Anonymous".into());
let upper = find_user(1).map(|n| n.to_uppercase());
let len = find_user(1).and_then(|n| Some(n.len()));

// Result<T, E>: Ok(value) or Err(error)
fn parse_num(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse()
}

match parse_num("42") {
    Ok(n) => println!("Parsed: {}", n),
    Err(e) => println!("Error: {}", e),
}

Traits & Trait Bounds

Traits definem comportamento compartilhado (como interfaces em outras linguagens). Tipos implementam traits com 'impl Trait for Type'. Traits podem ter implementações de método padrão. Trait bounds (<T: Trait>) restringem generics a tipos que implementam certos traits. A cláusula 'where' melhora legibilidade para bounds complexos. Traits habilitam polimorfismo via tanto static dispatch (generics) quanto dynamic dispatch (trait objects &dyn Trait).

rust
// define a trait (interface)
trait Summary {
    fn summarize(&self) -> String;

    // default method
    fn author(&self) -> String {
        String::from("Unknown")
    }
}

struct Article { title: String, content: String }

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}: {}", self.title, self.content)
    }
    // author() uses default implementation
}

let a = Article {
    title: "Rust".into(),
    content: "Great".into(),
};
println!("{}", a.summarize());
println!("Author: {}", a.author()); // Unknown

// generic with trait bound
fn print_summary<T: Summary>(item: &T) {
    println!("{}", item.summarize());
}

// multiple bounds with +, or where clause
fn display<T: Summary + std::fmt::Display>(item: &T) {}
// or: fn display<T>(item: &T) where T: Summary + std::fmt::Display {}

Derive Macros & Trait Objects

#[derive(...)] auto-implementa traits comuns: Debug (debug printing), Clone (deep copy), PartialEq/Eq (comparação ==), Hash (para chaves de HashMap), Copy (stack copy em vez de move). Trait objects (&dyn Trait ou Box<dyn Trait>) habilitam polimorfismo de runtime via vtable, a um pequeno custo de performance. Use generics para static dispatch (zero-cost) quando possível, trait objects quando precisar de coleções heterogêneas.

rust
// derive common traits automatically
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point {
    x: i32,
    y: i32,
}

let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone();          // Clone
println!("{:?}", p1);          // Debug
println!("{}", p1 == p2);      // PartialEq -> true

// trait object: dynamic dispatch
trait Animal {
    fn sound(&self) -> String;
}

struct Dog;
struct Cat;

impl Animal for Dog { fn sound(&self) -> String { "Woof".into() } }
impl Animal for Cat { fn sound(&self) -> String { "Meow".into() } }

// Vec of trait objects (dynamic dispatch via vtable)
let animals: Vec<Box<dyn Animal>> = vec![
    Box::new(Dog),
    Box::new(Cat),
];
for a in &animals {
    println!("{}", a.sound());
}
08

Tratamento de Erros

Result & Operador ?

O operador ? é a forma idiomática de propagar erros. Em Ok(v), ele desembrulha para v. Em Err(e), retorna Err(e) da função imediatamente. ? também converte tipos de erro via trait From, então funções retornando Box<dyn Error> podem usar ? em qualquer tipo de erro. Em Option, ? retorna None antecipadamente. Isso torna o tratamento de erros conciso sem sacrificar segurança.

rust
use std::fs;
use std::io;
use std::num::ParseIntError;

// ? propagates errors: if Err, return early; if Ok, unwrap
fn read_config(path: &str) -> Result<i32, io::Error> {
    let content = fs::read_to_string(path)?; // ? on io::Result
    Ok(content.len() as i32)
}

// ? converts error types via From
fn parse_and_read(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;     // io::Error -> Box
    let n: i32 = content.trim().parse()?;         // ParseIntError -> Box
    Ok(n)
}

// ? on Option too
fn first_char(s: &str) -> Option<char> {
    s.lines().next()?.chars().next()
}

Panic vs Result

Use panic! para erros irrecuperáveis (bugs, invariantes violados) — indica um erro de programação. Use Result para falhas esperadas e recuperáveis (entrada do usuário, I/O de arquivo, rede). unwrap()/expect() entram em panic em erro — aceitável em testes, protótipos ou quando você pode provar que o valor é válido. Em código de produção, prefira tratamento de erros adequado com ? e match.

rust
// panic: unrecoverable error, crashes the program
fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Division by zero!"); // unwinds stack
    }
    a / b
}

// Result: recoverable error, caller decides
fn safe_divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

// unwrap/expect: panic on Err (use sparingly)
let n: i32 = "42".parse().unwrap();       // panics on error
let n2: i32 = "42".parse().expect("valid number"); // panic with msg

// when to panic vs Result:
// panic: bugs, invariant violations, impossible states
// Result: expected failures (file not found, parse error)

Tipos de Erro Personalizados

Tipos de erro personalizados dão a você tratamento de erros type-safe e estruturado. Implemente Display (legível por humanos) e Error (para source chaining). Implemente From para cada tipo de erro subjacente para que ? converta automaticamente. Bibliotecas como thiserror (derive macro) ou anyhow (dynamic error boxes) reduzem boilerplate. Use thiserror para bibliotecas, anyhow para aplicações.

rust
use std::fmt;
use std::error::Error;

#[derive(Debug)]
enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
    NotFound(String),
}

// implement Display (required by Error)
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO error: {}", e),
            AppError::Parse(e) => write!(f, "Parse error: {}", e),
            AppError::NotFound(name) => write!(f, "Not found: {}", name),
        }
    }
}

// implement Error
impl Error for AppError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            AppError::Io(e) => Some(e),
            AppError::Parse(e) => Some(e),
            _ => None,
        }
    }
}

// From impls for ? to work automatically
impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}

Matching & Combinando Results

Result combinators permitem tratamento de erros estilo funcional sem match. map transforma o valor Ok, map_err transforma o erro, and_then encadeia operações falíveis (flatMap). unwrap_or fornece um default em erro. is_ok/is_err verificam sem consumir. Esses métodos tornam cadeias de tratamento de erros legíveis e evitam aninhamento profundo.

rust
// match on Result
let result: Result<i32, &str> = Ok(42);
match result {
    Ok(n) => println!("Got: {}", n),
    Err(e) => println!("Error: {}", e),
}

// combinators
let r1: Result<i32, &str> = Ok(5);
let doubled = r1.map(|n| n * 2);           // Ok(10)
let mapped_err = r1.map_err(|e| format!("{}", e));

// and_then: chain fallible operations
let r2 = r1.and_then(|n| if n > 0 { Ok(n * 2) } else { Err("negative") });

// unwrap_or family
let val = r1.unwrap_or(0);        // 5 or 0
let val2 = r1.unwrap_or_default(); // 5 or T::default()
let val3 = r1.unwrap_or_else(|_| 0); // lazy default

// check variants
println!("{}", r1.is_ok());  // true
println!("{}", r1.is_err()); // false

Convertendo Erros com From

O operador ? usa From para converter erros. Box<dyn Error> implementa From para todos os erros padrão, tornando-o um catch-all conveniente. O crate anyhow fornece anyhow::Result que adiciona contexto (ex.: .context("failed to read config")?). Para bibliotecas, defina um enum de erro específico com thiserror; para aplicações, use anyhow para simplicidade.

rust
use std::fs;
use std::num::ParseIntError;

// Without From: manual conversion needed
fn manual(path: &str) -> Result<i32, String> {
    let content = fs::read_to_string(path)
        .map_err(|e| e.to_string())?;  // manual convert
    let n: i32 = content.trim().parse()
        .map_err(|e| e.to_string())?;  // manual convert
    Ok(n)
}

// With Box<dyn Error>: auto-converts via From
fn boxed(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;  // auto-converts
    let n: i32 = content.trim().parse()?;      // auto-converts
    Ok(n)
}

// anyhow::Result (from anyhow crate) is ergonomic for apps
// fn anyhow_fn() -> anyhow::Result<i32> {
//     let n: i32 = something()?; // any error auto-converted
//     Ok(n)
// }
09

Módulos & Crates

Sistema de Módulos

O sistema de módulos do Rust organiza código. 'mod' declara um módulo (inline ou via arquivo). 'pub' torna itens públicos (padrão é privado). 'use' cria atalhos para paths. 'pub use' re-exporta itens (útil para design de API). O filesystem espelha a árvore de módulos: mod network pode ser src/network.rs ou src/network/mod.rs. Crate root é lib.rs (bibliotecas) ou main.rs (binários).

rust
// mod.rs or mod declaration in lib.rs/main.rs
// File: src/lib.rs
mod network {
    pub mod connection {
        pub fn connect() -> bool { true }
        fn disconnect() {} // private
    }
}

// use: bring paths into scope
use network::connection::connect;

// calling with full path
fn main() {
    network::connection::connect();
    connect(); // after 'use'
}

// re-export with pub use
pub use network::connection::connect as open_connection;

// nested file: src/network/connection.rs
// mod network; in lib.rs loads src/network/mod.rs
// which can have: pub mod connection;

Privacidade & Visibilidade

Privacidade em Rust é module-scoped. Itens são privados por padrão — apenas acessíveis dentro de seu módulo de definição e descendentes. 'pub' torna-os públicos. 'pub(crate)' restringe ao crate atual (útil para internals de biblioteca). 'pub(super)' restringe ao módulo parent. Campos de struct têm visibilidade individual — um pub struct pode ter campos privados, exigindo um construtor.

rust
mod my_module {
    // private by default
    fn internal_helper() {}

    // public: accessible from outside
    pub fn public_api() {
        internal_helper(); // can call private within module
    }

    // pub(crate): visible within this crate only
    pub(crate) fn crate_wide() {}

    // pub(super): visible to parent module
    pub(super) fn parent_visible() {}

    // struct fields are private even if struct is pub
    pub struct Config {
        pub name: String,    // public field
        secret: String,      // private field
    }

    // enum variants inherit the enum's visibility
    pub enum Status {
        Active,  // public because Status is public
        Inactive,
    }
}

Use Statements & Aliasing

use statements trazem itens para escopo, reduzindo verbosidade de path. Imports agrupados (use std::io::{self, Read}) são mais limpos que múltiplas linhas. Traits devem estar em escopo para usar seus métodos — é por isso que às vezes você precisa 'use std::io::Read' mesmo se não referencia Read pelo nome. Glob imports (*) são desencorajados exceto para preludes.

rust
// basic use
use std::collections::HashMap;
use std::fs::read_to_string;

// grouped use
use std::io::{self, Read, Write, BufRead};
// equivalent to:
// use std::io;
// use std::io::Read;
// use std::io::Write;
// use std::io::BufRead;

// aliasing with 'as'
use std::collections::HashMap as Map;

// glob import (use sparingly)
use std::prelude::v1::*;

// bringing trait methods into scope
use std::io::Read; // now .read() method is available
let mut f = std::fs::File::open("x")?;
let mut buf = String::new();
f.read_to_string(&mut buf)?; // Read trait method

Cargo & Crates Externas

Cargo é o gerenciador de pacotes e build system do Rust. Dependências vão em Cargo.toml sob [dependencies]. Features habilitam funcionalidade opcional (reduzindo tempo de compilação/binary size). 'cargo add' auto-edita Cargo.toml. Crates são publicadas em crates.io. Edition (2021) controla recursos de linguagem. Cargo lida com compilação, testes, documentação e publicação.

rust
# Cargo.toml
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
rand = "0.8"

# in Rust code
use serde::{Serialize, Deserialize};
use rand::Rng;

#[derive(Serialize, Deserialize)]
struct User { name: String, age: u32 }

fn main() {
    let mut rng = rand::thread_rng();
    let n: i32 = rng.gen_range(1..=100);
    println!("{}", n);
}

# CLI commands:
# cargo new my_app      # create new project
# cargo build           # compile
# cargo run             # compile + run
# cargo test            # run tests
# cargo add serde       # add dependency
# cargo update          # update dependencies

Testes

Testes usam atributo #[test]. assert_eq!/assert_ne! comparam valores. #[should_panic] verifica que um panic ocorre. Testes podem retornar Result para asserções baseadas em erro. #[cfg(test)] garante que o módulo de testes apenas compile durante testes. Testes unitários vivem junto ao código; testes de integração vão no diretório tests/. Execute com 'cargo test'. Use #[ignore] para pular testes flaky.

rust
// Unit tests in same file (convention: tests module)
pub fn add(a: i32, b: i32) -> i32 { a + b }

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
        assert_ne!(add(2, 3), 6);
    }

    #[test]
    fn test_with_message() {
        let result = add(1, 1);
        assert_eq!(result, 2, "Expected 2, got {}", result);
    }

    #[test]
    #[should_panic(expected = "division by zero")]
    fn test_panic() {
        panic!("division by zero");
    }

    #[test]
    fn test_result() -> Result<(), String> {
        if add(2, 2) == 4 { Ok(()) }
        else { Err(String::from("math is broken")) }
    }
}

// Integration tests: tests/integration_test.rs
// Run: cargo test
10

Concorrência & File I/O

Threads

std::thread::spawn cria OS threads. A closure deve ser 'move' se captura variáveis, transferindo ownership para a thread (prevenindo use-after-free). join() bloqueia até a thread completar, retornando um Result. O sistema de ownership do Rust previne data races em tempo de compilação — você não pode compartilhar dados mutáveis entre threads sem sincronização (Arc<Mutex<T>>).

rust
use std::thread;
use std::time::Duration;

// spawn a thread
let handle = thread::spawn(|| {
    for i in 0..5 {
        println!("spawned thread: {}", i);
        thread::sleep(Duration::from_millis(10));
    }
});

// main thread continues
for i in 0..3 {
    println!("main thread: {}", i);
}

// wait for spawned thread to finish
handle.join().unwrap();

// move closure: transfer ownership to thread
let data = vec![1, 2, 3];
let h = thread::spawn(move || {
    println!("got data: {:?}", data); // data moved here
});
h.join().unwrap();

Channels (Message Passing)

Channels habilitam comunicação entre threads via message passing (lema do Go: 'Share memory by communicating'). mpsc permite múltiplos senders (tx.clone()) mas um receiver. send() retorna Result (Err se receiver dropped). O receiver implementa Iterator, então loops for funcionam naturalmente. Para múltiplos receivers, use crossbeam-channel ou async channels. Message passing evita a complexidade de estado mutável compartilhado.

rust
use std::sync::mpsc;
use std::thread;

// mpsc: multiple producer, single consumer
let (tx, rx) = mpsc::channel();

// clone transmitter for multiple producers
let tx2 = tx.clone();

thread::spawn(move || {
    let vals = vec!["a", "b", "c"];
    for v in vals {
        tx.send(v).unwrap();
    }
});

thread::spawn(move || {
    tx2.send("from tx2").unwrap();
});

// receiver is an iterator
for received in rx {
    println!("Got: {}", received);
}

Mutex & Arc (Estado Compartilhado)

Arc (Atomic Reference Counted) habilita ownership compartilhado entre threads (Rc thread-safe). Mutex fornece acesso exclusivo — lock() bloqueia até adquirido, retorna um MutexGuard que libera o lock em drop. RwLock permite múltiplos leitores ou um escritor. A combinação Arc<Mutex<T>> é o padrão padrão para estado mutável compartilhado. unwrap() em lock() lida com poison (uma thread entrou em panic enquanto mantinha o lock).

rust
use std::sync::{Arc, Mutex};
use std::thread;

// Arc: thread-safe reference counting
// Mutex: mutual exclusion lock
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        // lock() returns MutexGuard, auto-unlocks when dropped
        let mut num = counter.lock().unwrap();
        *num += 1;
    }); // lock released here
    handles.push(handle);
}

for h in handles { h.join().unwrap(); }

println!("Result: {}", *counter.lock().unwrap()); // 10

// RwLock: multiple readers OR one writer
use std::sync::RwLock;
let data = RwLock::new(5);
let r1 = data.read().unwrap();  // shared read
let r2 = data.read().unwrap();
// let w = data.write().unwrap(); // would block until r1, r2 dropped

File I/O

fs::read_to_string é conveniente para arquivos pequenos. Para arquivos grandes, use BufReader/BufWriter para reduzir system calls. Traits Read/Write fornecem operações de byte de baixo nível. BufRead adiciona lines() e read_line() para texto. Sempre trate erros com ? (arquivos podem estar ausentes, permissões negadas, disco cheio). flush() garante que dados buffered alcancem o OS (embora não necessariamente o disco).

rust
use std::fs;
use std::io::{Read, Write, BufReader, BufWriter};
use std::fs::File;

// read entire file
let content = fs::read_to_string("file.txt")?;
fs::write("output.txt", "Hello")?;

// buffered read (efficient for large files)
let file = File::open("file.txt")?;
let mut reader = BufReader::new(file);
let mut buf = String::new();
reader.read_to_string(&mut buf)?;

// line by line
use std::io::BufRead;
let file = File::open("file.txt")?;
for line in BufReader::new(file).lines() {
    println!("{}", line?);
}

// buffered write
let file = File::create("out.txt")?;
let mut writer = BufWriter::new(file);
writeln!(writer, "Line 1")?;
writeln!(writer, "Line 2")?;
writer.flush()?; // ensure written to disk

Async/Await (Tokio)

Async/await habilita concorrência eficiente sem OS threads — tasks rodam em um thread pool e fazem yield em pontos .await. tokio é o runtime async mais popular. async fn retorna um Future que deve ser .awaited. tokio::join! roda futures concorrentemente e espera todas. tokio::select! faz race de futures. Async é ideal para trabalho I/O-bound (rede, arquivos); use threads para trabalho CPU-bound. O .await não bloqueia a thread — devolve controle ao runtime.

rust
use tokio::{fs, task, time};
use std::time::Duration;

// async fn returns a Future
async fn fetch_data() -> String {
    time::sleep(Duration::from_secs(1)).await;
    String::from("data")
}

// spawn concurrent tasks
#[tokio::main]
async fn main() {
    // run tasks concurrently
    let (a, b, c) = tokio::join!(
        fetch_data(),
        fetch_data(),
        fetch_data()
    );
    println!("{} {} {}", a, b, c);

    // spawn a background task
    let handle = tokio::spawn(async {
        let data = fs::read_to_string("file.txt").await.unwrap();
        println!("Read {} bytes", data.len());
    });
    handle.await.unwrap();

    // select: first to complete wins
    tokio::select! {
        val = fetch_data() => println!("Got: {}", val),
        _ = time::sleep(Duration::from_millis(500)) => {
            println!("Timeout!");
        }
    }
}
11

Aprofundamento em Lifetimes

Named Lifetimes em Funções

Lifetimes são a forma do compilador rastrear validade de referência. O 'a é um parâmetro de lifetime genérico — não muda comportamento de runtime, apenas verificação em tempo de compilação. Quando uma função recebe múltiplas referências e retorna uma, você deve anotar lifetimes para que o compilador saiba que a referência retornada não outlive seus inputs. Isso previne dangling pointers em tempo de compilação.

rust
// Lifetimes tell the compiler how long references live
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
// 'a means: the returned reference lives as long as
// the SHORTEST of x and y

let s1 = String::from("long string");
let result;
{
    let s2 = String::from("hi");
    result = longest(s1.as_str(), s2.as_str());
    // result valid here
    println!("{}", result);
}
// result NOT valid here — s2 is dropped

Regras de Lifetime Elision

Regras de lifetime elision permitem omitir anotações de lifetime explícitas em casos comuns. Regra 1 atribui um lifetime distinto a cada parâmetro de referência. Regra 2 atribui esse lifetime à saída se houver exatamente uma referência de input. Regra 3 aplica-se a métodos — a saída obtém o lifetime de &self. Quando nenhuma dessas resolve todas as referências, você deve escrever lifetimes explícitas. A maioria do código Rust idiomático raramente precisa de anotações de lifetime explícitas.

rust
// The compiler applies 3 elision rules automatically:
// 1. Each input reference gets its own lifetime
// 2. If one input lifetime, output gets that lifetime
// 3. If &self/&mut self, output gets self's lifetime

// These DON'T need explicit annotations (rule 2):
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' { return &s[0..i]; }
    }
    &s[..]
}

// This NEEDS annotation (multiple inputs, rule doesn't apply):
// fn longest<'a>(x: &'a str, y: &'a str) -> &'a str

O Lifetime 'static

'static é o lifetime mais longo — dura toda a duração do programa. Todos os string literals têm esse lifetime porque são embedded no binário. Quando você vê T: 'static como bound, não significa que T deve viver para sempre — significa que T não deve conter referências mais curtas que 'static (ou seja, tipos owned como String, Vec, i64 sempre satisfazem isso). Threads requerem 'static porque podem outlive a função chamadora.

rust
// 'static means the reference lives for the entire program
let s: &'static str = "I live forever";
// All string literals are &'static str

// Static variables (global, program-wide)
static COUNTER: AtomicUsize = AtomicUsize::new(0);
COUNTER.fetch_add(1, Ordering::SeqCst);

// 'static in bounds: T: 'static means T contains no
// non-static references (owned data always satisfies this)
fn spawn_thread<T: Send + 'static>(t: T) {
    std::thread::spawn(move || {
        drop(t);
    });
}

Lifetimes em Structs

Quando um struct mantém uma referência (não um tipo owned), ele precisa de um parâmetro de lifetime para declarar por quanto tempo essa referência é válida. A instância do struct não pode outlive os dados que pega emprestado. Isso é comum para parsers zero-copy, iteradores sobre dados borrowed e views. O lifetime deve ser declarado em tanto o struct quanto seu block impl. Prefira possuir dados (String, Vec) a menos que tenha um motivo específico para pegar emprestado.

rust
// Structs holding references need lifetime annotations
struct Parser<'a> {
    text: &'a str,
    pos: usize,
}

impl<'a> Parser<'a> {
    fn new(text: &'a str) -> Self {
        Parser { text, pos: 0 }
    }
    fn peek(&self) -> Option<char> {
        self.text[self.pos..].chars().next()
    }
    fn advance(&mut self) {
        self.pos += 1;
    }
}

// The struct cannot outlive the text it borrows
let text = String::from("hello");
let mut p = Parser::new(&text);
p.advance();

Múltiplas Lifetimes & Subtyping

Funções com múltiplas referências que interagem diferentemente precisam de múltiplos parâmetros de lifetime. Lifetime subtyping significa que um lifetime mais longo pode substituir um mais curto (covariância) — &'static pode ser usado onde quer que &'a seja esperado. É por isso que 'static é um subtipo de todos os lifetimes. Use múltiplas lifetimes quando o lifetime da saída depende apenas de alguns inputs, dando ao compilador mais flexibilidade e ao chamador menos constraints.

rust
// Multiple lifetime parameters
fn parse<'src, 'ctx>(src: &'src str, ctx: &'ctx Context) -> &'src str {
    // returns something tied to src, not ctx
    src
}

// 'static: 'a is always true (static outlives everything)
fn static_ref<'a>(_: &'a str) -> &'static str {
    "constant"  // 'static coerces to 'a
}

// Variance: &'long can be used where &'short is expected
// (covariance) — longer lifetime is a subtype
fn use_ref<'short>(r: &'short str) {}
let long: &'static str = "hi";
use_ref(long);  // OK: 'static coerces to any 'short
12

Smart Pointers (Box, Rc, Arc, RefCell)

Box<T> — Alocação no Heap

Box<T> é o smart pointer mais simples do Rust — ele heap-allocate um valor com ownership único. Use-o para tipos recursivos (cujo tamanho não pode ser conhecido em tempo de compilação), valores grandes que você não quer mover na stack e trait objects (Box<dyn Trait>) para dynamic dispatch. Box derefs para T então se comporta como o valor interno. Tem essencialmente zero overhead além da alocação heap em si.

rust
// Box moves data to the heap (single owner)
let b = Box::new(5);
println!("{}", b);  // derefs automatically

// Recursive types NEED Box (size unknown at compile time)
enum List {
    Cons(i32, Box<List>),
    Nil,
}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));

// Trait objects (dynamic dispatch)
let shapes: Vec<Box<dyn Draw>> = vec![
    Box::new(Circle { radius: 1.0 }),
    Box::new(Square { side: 2.0 }),
];
for s in &shapes { s.draw(); }

// Box has zero runtime overhead when dereferencing

Rc<T> — Reference Counting (Single Thread)

Rc<T> (Reference Counted) habilita ownership múltiplo para cenários single-threaded. Rc::clone incrementa a contagem de referência em vez de copiar dados — cópia de pointer barata. O valor é dropped quando o último Rc é dropped. Use Rc para nós de grafo compartilhados, árvores parent-child ou qualquer estrutura onde múltiplas partes precisam possuir os mesmos dados. Rc NÃO é thread-safe — use Arc para multithreading. Rc é imutável — você não pode mutar através dele diretamente.

rust
use std::rc::Rc;

// Rc allows multiple owners via reference counting
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a);  // increments count, doesn't copy
let c = Rc::clone(&a);

println!("count = {}", Rc::strong_count(&a));  // 3
// Data dropped when count reaches 0

// Shared graph/tree structures
struct Node {
    children: Vec<Rc<Node>>,
    value: i32,
}
let leaf = Rc::new(Node { children: vec![], value: 1 });
let branch = Rc::new(Node {
    children: vec![Rc::clone(&leaf)],
    value: 2,
});

Arc<T> — Atomic Reference Counting (Thread-Safe)

Arc<T> (Atomically Reference Counted) é a contraparte thread-safe do Rc. Usa operações atômicas para reference counting, tornando-o seguro para compartilhar entre threads. A desvantagem é um pouco mais de overhead que Rc. Use Arc sempre que precisar de ownership compartilhado entre múltiplas threads. Para mutar dados compartilhados, combine Arc com Mutex (para acesso exclusivo) ou RwLock (para acesso read-heavy). Arc::clone é barato — apenas incrementa um contador atômico.

rust
use std::sync::Arc;
use std::thread;

// Arc: thread-safe version of Rc (atomic ref counting)
let data = Arc::new(vec![1, 2, 3, 4, 5]);

let handles: Vec<_> = (0..3).map(|i| {
    let data = Arc::clone(&data);  // atomic increment
    thread::spawn(move || {
        println!("Thread {} sees: {:?}", i, *data);
    })
}).collect();

for h in handles { h.join().unwrap(); }

// Arc is slightly slower than Rc due to atomic operations
// Only use Arc when actually sharing across threads

RefCell<T> — Interior Mutability

RefCell<T> fornece interior mutability — você pode mutar através de uma referência compartilhada, com regras de borrow aplicadas em runtime em vez de tempo de compilação. borrow() retorna uma referência imutável, borrow_mut() uma mutável. Violar as regras (ex.: dois borrows mutáveis) causa panic em runtime. Use RefCell quando o compilador não pode provar borrow safety (ex.: estruturas de grafo, mock objects em testes). Rc<RefCell<T>> é o padrão clássico para dados mutáveis compartilhados single-threaded.

rust
use std::cell::RefCell;

// RefCell moves borrow checking to RUNTIME
let cell = RefCell::new(vec![1, 2, 3]);

// Multiple immutable borrows OR one mutable borrow
{
    let mut borrowed = cell.borrow_mut();
    borrowed.push(4);
}  // borrow released here

{
    let r1 = cell.borrow();     // OK
    let r2 = cell.borrow();     // OK — multiple immutable
    println!("{:?} {:?}", r1, r2);
}

// cell.borrow_mut() while r1 alive → PANIC at runtime
// Combine with Rc: Rc<RefCell<T>> for mutable shared graphs

Weak<T> — Quebrando Ciclos de Referência

Weak<T> é uma referência non-owning que não afeta a contagem de strong reference. Isso é essencial para quebrar ciclos de referência: se um parent possui children (Rc) e children possuem o parent (Rc), nenhum dos dois será liberado — um memory leak. A solução é tornar a back-reference Weak. upgrade() retorna Option<Rc<T>> — None se o valor já foi dropped. Use Weak para links child→parent, caches e padrões observer onde você não quer manter dados vivos.

rust
use std::rc::{Rc, Weak, RefCell};

// Weak references don't contribute to the strong count
// — prevents memory leaks in cycles
struct Node {
    parent: RefCell<Weak<Node>>,        // weak: child → parent
    children: RefCell<Vec<Rc<Node>>>,   // strong: parent → children
}

let leaf = Rc::new(Node {
    parent: RefCell::new(Weak::new()),
    children: RefCell::new(vec![]),
});
let branch = Rc::new(Node {
    parent: RefCell::new(Weak::new()),
    children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);

// Upgrade returns Option — parent may already be dropped
if let Some(p) = leaf.parent.borrow().upgrade() {
    println!("parent exists");
}
13

Trait Objects & Dynamic Dispatch

dyn Trait — Dynamic Dispatch

dyn Trait habilita dynamic dispatch — o tipo concreto é apagado em tempo de compilação e chamadas de método vão através de uma vtable em runtime. Isso permite armazenar tipos heterogêneos em uma única coleção (Vec<Box<dyn Animal>>). A desvantagem: um pequeno custo de runtime (vtable indirection, sem inlining) e o tipo não pode ser conhecido em tempo de compilação. Use trait objects quando o conjunto de tipos concretos não é conhecido em tempo de compilação ou quando precisa agrupar tipos diferentes.

rust
trait Animal {
    fn name(&self) -> &str;
    fn sound(&self) -> String;
}

struct Dog { name: String }
struct Cat { name: String }

impl Animal for Dog {
    fn name(&self) -> &str { &self.name }
    fn sound(&self) -> String { "Woof".into() }
}
impl Animal for Cat {
    fn name(&self) -> &str { &self.name }
    fn sound(&self) -> String { "Meow".into() }
}

// dyn Trait = erased type, runtime dispatch via vtable
let animals: Vec<Box<dyn Animal>> = vec![
    Box::new(Dog { name: "Rex".into() }),
    Box::new(Cat { name: "Whiskers".into() }),
];
for a in &animals {
    println!("{} says {}", a.name(), a.sound());
}

Regras de Object Safety

Um trait é object-safe apenas se o compilador pode construir uma vtable para ele. Duas regras: (1) métodos não devem retornar Self (o tipo concreto é apagado, então não pode ser conhecido), e (2) métodos não devem ter parâmetros de tipo genérico (a vtable precisaria de uma entrada para cada tipo possível). Traits com Sized como supertrait também não são object-safe. Se precisar de object safety, refatore métodos que retornam Self para retornar Box<dyn Trait> ou use uma função factory separada.

rust
// Object-safe traits CAN be used as dyn Trait:
trait Draw {
    fn draw(&self);  // OK: &self, no generics
}

// NOT object-safe:
trait Bad {
    fn create() -> Self;        // returns Self — needs known type
    fn process<T>(&self, x: T); // generic method — vtable can't cover all T
    const SIZE: usize;          // associated const (sometimes OK)
}

// Workaround for Self returns — use a factory or Box<Self>
trait GoodFactory {
    fn new_boxed() -> Box<dyn GoodFactory>;
}

// Sized bound makes trait non-object-safe
// trait Foo: Sized {}  // NOT object-safe

Trait Objects vs Generics

Generics usam monomorphization — o compilador gera uma cópia separada da função para cada tipo concreto, habilitando static dispatch e otimização total (inlining). Isso tem zero custo de runtime, mas aumenta o binary size. Trait objects (dyn) usam uma única função com vtable lookup — binary menor, mas um pequeno custo de runtime por chamada. Escolha generics quando performance importa e o conjunto de tipos é pequeno/conhecido; escolha dyn quando precisa de coleções heterogêneas ou não conhece todos os tipos antecipadamente.

rust
// Generics: monomorphization, static dispatch, zero overhead
fn max_generic<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}
// Each type instantiation creates a separate function:
// max_generic::<i32>, max_generic::<f64>, etc.

// Trait objects: dynamic dispatch, single function
fn max_dyn(a: &dyn PartialOrd, b: &dyn PartialOrd) -> bool {
    // can't return — don't know size at compile time
    a.partial_cmp(b) == Some(std::cmp::Ordering::Less)
}

// Rule of thumb:
// - Few types, performance-critical → generics (static dispatch)
// - Many/unknown types, flexibility needed → dyn (dynamic dispatch)
// - Heterogeneous collections → must use dyn

Any Trait & Downcasting

A trait Any permite armazenar valores de qualquer tipo e recuperar o tipo concreto em runtime via downcasting. downcast_ref::<T>() retorna Option<&T>, downcast::<T>() retorna Result<Box<T>, Box<dyn Any>>. Esse é o escape hatch do Rust para quando você verdadeiramente não conhece o tipo em tempo de compilação (plugin systems, configs dinâmicas). No entanto, prefira enums quando o conjunto de tipos possíveis é conhecido — eles são mais seguros, mais rápidos e mais idiomáticos. Any depende de TypeId, que é implementado para todos os tipos 'static.

rust
use std::any::Any;

// Any enables runtime type checking & downcasting
let x: Box<dyn Any> = Box::new(42i32);

// downcast_ref returns Option<&T>
if let Some(n) = x.downcast_ref::<i32>() {
    println!("It's an i32: {}", n);
}

// downcast returns Option<T> (for Box)
let boxed: Box<dyn Any> = Box::new("hello");
if let Ok(s) = boxed.downcast::<&str>() {
    println!("Recovered: {}", s);
}

// Useful for: plugin systems, error types, heterogeneous storage
// Avoid overusing — prefer enums when the type set is known

Default Trait Methods & Supertraits

Traits podem fornecer implementações de método padrão que implementadores podem sobrescrever ou usar como estão. Supertraits (trait Named: Shape) exigem que o tipo implementador também implemente o supertrait — isso cria uma hierarquia onde tipos Named são garantidos ter area() e describe(). Default methods reduzem boilerplate e habilitam o padrão 'extension method' onde adicionar um método a um trait beneficia automaticamente todos os implementadores existentes sem quebrá-los.

rust
trait Shape {
    fn area(&self) -> f64;
    // Default method — can be overridden
    fn describe(&self) -> String {
        format!("Shape with area {:.2}", self.area())
    }
}

// Supertrait: trait that requires another trait
trait Named: Shape {
    fn name(&self) -> &str;
}

struct Circle { radius: f64 }
impl Shape for Circle {
    fn area(&self) -> f64 { std::f64::consts::PI * self.radius.powi(2) }
}
impl Named for Circle {
    fn name(&self) -> &str { "Circle" }
}

let c = Circle { radius: 2.0 };
println!("{}", c.describe());  // uses default method
14

Macros (Declarative & Procedural)

Declarative Macros (macro_rules!)

macro_rules! cria macros declarativas que se expandem em tempo de compilação via correspondência de padrões. A sintaxe $(...),* é um correspondedor de repetição — corresponde a zero ou mais expressões separadas por vírgulas. $x:expr significa 'corresponder a qualquer expressão e vinculá-la a x'. As macros são expandidas antes da verificação de tipos, então podem gerar código que funciona com qualquer tipo. Use macros para reduzir boilerplate que generics não conseguem tratar (ex.: argumentos variádicos, extensão de sintaxe).

rust
// macro_rules! defines pattern-matching macros
macro_rules! vec_of {
    ($($x:expr),*) => {{
        let mut v = Vec::new();
        $( v.push($x); )*
        v
    }};
}

let nums = vec_of!(1, 2, 3, 4);

// Recursive macro: build a HashMap
macro_rules! hashmap {
    ($($k:expr => $v:expr),*) => {{
        let mut m = std::collections::HashMap::new();
        $( m.insert($k, $v); )*
        m
    }};
}
let config = hashmap!("host" => "localhost", "port" => 8080);

Tipos de Fragmento de Macro

Os especificadores de fragmento determinam que tipo de sintaxe um argumento de macro corresponde. :ident corresponde a identificadores (nomes), :expr corresponde a expressões (valores), :ty corresponde a tipos, :block corresponde a blocos delimitados por chaves, :stmt corresponde a instruções, :literal corresponde a literais. Escolher o especificador certo importa — :expr é o mais comum, mas :ident é necessário quando você quer criar um nome de função/variável. O sistema de macros é higênico: identificadores introduzidos por macros não colidem com o código circundante.

rust
// Common fragment specifiers:
macro_rules! build_fn {
    // $name:ident — identifier (function/variable name)
    // $body:block — a block { ... }
    // $ty:ty — a type
    // $expr:expr — an expression
    // $stmt:stmt — a statement
    // $lit:literal — a literal (string, number)
    ($name:ident, $ret:ty, $body:block) => {
        fn $name() -> $ret $body
    };
}

build_fn!(get_answer, i32, { 42 });
println!("{}", get_answer());

// :pat — pattern, :path — module path
// :meta — attribute meta-item, :vis — visibility

Macros Padrão Integradas

Rust vem com muitas macros integradas. println!/eprintln! imprimem para stdout/stderr. dbg! imprime o valor de uma expressão com informações de arquivo/linha — ótimo para depuração (também retorna o valor). assert!/assert_eq!/assert_ne! são para testes e invariantes. todo!/unimplemented! marcam código incompleto com um panic. file!/line!/module! fornecem informações de localização em tempo de compilação. env!/option_env! leem variáveis de ambiente em tempo de compilação — útil para incorporar informações de versão.

rust
// Formatting & printing
println!("x = {}, y = {:?}", 1, "two");
eprintln!("Error: {}", "oops");     // stderr
format!("{}-{}", "a", "b");          // returns String

// Debug helpers
dbg!(2 + 2);                          // prints [src.rs:1] 2 + 2 = 4
assert!(1 + 1 == 2);                  // panic if false
assert_eq!(2 + 2, 4);                 // panic if not equal
assert_ne!(1, 2);                     // panic if equal

// Code generation
todo!("not implemented yet");         // unimplemented!()
unimplemented!();
panic!("fatal: {}", "reason");

// Environment & file info
println!("{}:{}", file!(), line!());  // src.rs:1
println!("{}", env!("CARGO_PKG_NAME"));

Visão Geral de Macros Procedurais

Macros procedurais (proc macros) são funções Rust que recebem TokenStreams como entrada e produzem TokenStreams como saída — transformação completa de código para código. Ao contrário de macros declarativas, elas podem fazer computação arbitrária. Três tipos: macros derive (adicionam implementações de trait via #[derive]), macros de atributo (anotam itens) e macros semelhantes a funções (sintaxe personalizada como sqlx::query!). Elas devem viver em um crate separado com proc-macro = true. O crate syn analisa a sintaxe Rust, quote! gera código. Exemplos populares: serde, tokio, thiserror.

rust
// Procedural macros are Rust functions that transform code
// Three kinds (must be in a separate crate with proc-macro=true):

// 1. Derive macros — #[derive(MyTrait)]
#[derive(Debug, Clone, MyTrait)]
struct Point { x: f64, y: f64 }

// 2. Attribute macros — #[my_attr]
#[my_attr]
fn function() {}

// 3. Function-like macros — my_macro!(...)
sqlx::query!("SELECT * FROM users");

// Cargo.toml for a proc-macro crate:
// [lib]
// proc-macro = true
//
// [dependencies]
// syn = "2.0"   # parse Rust code
// quote = "1.0" # generate code
// proc-macro2 = "1.0"

Higiene de Macros & Padrões Comuns

As macros Rust são higênicas — identificadores criados dentro de uma macro existem em um 'contexto de sintaxe' separado e não capturam ou fazem shadow de variáveis no escopo de chamada acidentalmente. Isso evita bugs sutis onde o nome de variável interno de uma macro colide com o do chamador. stringify! converte qualquer fluxo de tokens em um literal de string em tempo de compilação (útil para mensagens de erro). cfg_debug! mostra um padrão comum: compilar condicionalmente código com base na configuração de build usando o sistema cfg!.

rust
// Hygiene: macro-introduced identifiers don't leak
macro_rules! using_temp {
    ($e:expr) => {
        let temp = $e;  // this 'temp' is distinct from caller's
        println!("{}", temp);
    };
}
let temp = 10;
using_temp!(temp + 5);  // no conflict — hygienic

// Conditional compilation macro
macro_rules! cfg_debug {
    ($($e:tt)*) => {
        #[cfg(debug_assertions)]
        { $($e)* }
    };
}
cfg_debug! {
    println!("Debug mode on");
}

// stringify! converts tokens to a string literal
let s = stringify!(a + b * c);  // "a + b * c"
15

Rust Inseguro (Unsafe)

Ponteiros Crus (Raw Pointers)

Ponteiros crus (*const T, *mut T) são a saída de escape do Rust do borrow checker. Ao contrário de referências, eles podem ser nulos, podem ter alias (múltiplos ponteiros para os mesmos dados) e não rastream lifetimes. Criá-los é seguro, mas desreferenciar requer unsafe porque o compilador não pode garantir validade. Use ponteiros crus para FFI (interfacear com C), implementar estruturas de dados de baixo nível (listas encadeadas, vetores) e código crítico de desempenho onde você garante a segurança manualmente. Sempre documente por que unsafe é sound.

rust
// Raw pointers: *const T (immutable) and *mut T (mutable)
let x = 42;
let r1: *const i32 = &x;       // coerce from reference
let r2: *mut i32 = x as *mut i32;  // cast

// Can be null, can alias, no borrow checking
let null: *const i32 = std::ptr::null();

// Creating raw pointers is safe, but DEREFERENCING is unsafe
unsafe {
    println!("r1 = {}", *r1);
}

// Convert between types (transmute-like)
let bytes: [u8; 4] = [0x78, 0x56, 0x34, 0x12];
let ptr = bytes.as_ptr() as *const u32;
unsafe { println!("0x{:x}", *ptr); }  // little-endian int

Blocos & Funções Unsafe

unsafe não desliga o borrow checker — permite que você faça cinco coisas específicas: (1) desreferenciar ponteiros crus, (2) chamar funções unsafe, (3) implementar traits unsafe, (4) acessar/mutar static mut, (5) acessar campos de union. Blocos unsafe tornam as operações unsafe explícitas e localizadas. unsafe fn declara que chamar a função requer manter invariantes que o compilador não consegue verificar. get_unchecked pula a verificação de limites para desempenho — só é seguro se você verificou o índice. Minimize a área de superfície unsafe e encapsule-a atrás de uma API segura.

rust
// unsafe block: a localized unsafe region
let ptr: *const i32 = &42;
let val = unsafe { *ptr };

// unsafe fn: the ENTIRE function body is unsafe
unsafe fn dangerous(ptr: *const u8) -> u8 {
    *ptr
}
// Callers must use unsafe block:
let b = 5u8;
unsafe { dangerous(&b) };

// Splitting borrows safely (compiler is conservative)
let mut v = vec![1, 2, 3, 4];
let len = v.len();
unsafe {
    let first = v.get_unchecked(0);   // no bounds check
    let last = v.get_unchecked(len - 1);
    println!("{} {}", first, last);
}

FFI — Chamando Funções C

FFI (Foreign Function Interface) permite que Rust chame funções C e vice-versa. Blocos extern "C" declaram funções C externas — chamá-las é unsafe porque o compilador não pode verificar seu comportamento. #[no_mangle] impede que Rust renomeie a função para que C possa encontrá-la pelo nome. #[repr(C)] garante que o layout da struct corresponda ao layout de memória do C (Rust pode reordenar campos por padrão para eficiência). Use FFI para chamadas de sistema, bibliotecas legadas e bindings críticos de desempenho. O crate bindgen gera automaticamente declarações FFI a partir de cabeçalhos C.

rust
// extern "C" declares foreign functions
extern "C" {
    fn abs(x: i32) -> i32;
}

fn main() {
    let x = -5;
    let positive = unsafe { abs(x) };
    println!("{}", positive);  // 5
}

// Exporting Rust functions to C
#[no_mangle]  // prevent name mangling
pub extern "C" fn add(a: i64, b: i64) -> i64 {
    a + b
}

// C-compatible struct
#[repr(C)]
struct Point { x: f64, y: f64 }

Implementando Traits Unsafe

Traits unsafe (como Send, Sync) exigem que o implementador mantenha invariantes que o compilador não consegue verificar. Send significa que um tipo pode mover com segurança entre threads; Sync significa que &T pode ser compartilhado entre threads. O compilador deriva automaticamente esses para a maioria dos tipos, mas ponteiros crus não são Send/Sync por padrão. Quando você os implementa manualmente, assume a responsabilidade pela thread safety. static mut requer acesso unsafe porque múltiplas threads podem competir por ele — prefira atomics (AtomicU64) ou Mutex. Sempre documente a justificativa de segurança com um comentário SAFETY.

rust
// Some traits are unsafe to implement — the compiler can't verify invariants
use std::marker::Send;

// Send/Sync are auto-implemented, but sometimes you must manually impl
struct RawPointer<T>(*mut T);

// SAFETY: We guarantee the pointer is only used on one thread
unsafe impl<T> Send for RawPointer<T> where T: Send {}

// Splitting a slice into disjoint mutable parts
let mut v = vec![1, 2, 3, 4, 5, 6];
let (left, right) = v.split_at_mut(3);
// left = [1,2,3], right = [4,5,6] — disjoint, but compiler
// couldn't prove this without unsafe internally

// Global mutable state
static mut COUNTER: u64 = 0;
unsafe { COUNTER += 1; }  // unsafe: data races possible

Unions & Assembly Inline

Unions permitem que diferentes tipos compartilhem o mesmo local de memória — ler um campo que não foi o último escrito é comportamento indefinido, portanto unsafe. Elas são principalmente para FFI com C. transmute faz reinterpret-cast do padrão de bits de um tipo para outro do mesmo tamanho — extremamente perigoso se os tamanhos diferirem ou os tipos forem incompatíveis. Assembly inline (asm!) permite incorporar instruções de CPU diretamente, útil para desenvolvimento de kernel e otimização extrema. Todas essas são ferramentas afiadas: use apenas quando nenhuma alternativa segura existir e encapsule atrás de uma abstração segura.

rust
// Unions: multiple fields share the same memory (like C unions)
#[repr(C)]
union IntOrFloat {
    i: i32,
    f: f32,
}

let mut u = IntOrFloat { i: 42 };
unsafe { println!("as int: {}", u.i); }
u.f = 3.14;
unsafe { println!("as float: {}", u.f); }
// Reading the WRONG field is undefined behavior!

// Inline assembly (nightly / asm!)
#[cfg(feature = "asm")]
unsafe fn halt() {
    std::arch::asm!("hlt", options(nostack));
}

// Transmute: reinterpret bits as another type (same size)
let bits: u32 = 0x40490FDB;  // ~3.14159 in IEEE 754
let pi: f32 = unsafe { std::mem::transmute(bits) };
16

Iteradores Aprofundado

Trait Iterator & Criação

O trait Iterator requer apenas um método next() que retorna Option<Item> — None sinaliza exaustão. Todo o resto (map, filter, collect) é construído em cima. iter() empresta elementos (&T), into_iter() consome a coleção (produz T possuído), iter_mut() produz &mut T. Ranges (1..5, 1..=5) são iteradores diretamente. Strings iteram por chars (valores escalares Unicode) ou bytes. Iteradores são lazy — nada é executado até você consumi-los.

rust
// The Iterator trait: one required method
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ... many provided methods (map, filter, etc.)
}

// Creating iterators
let v = vec![1, 2, 3];
let iter = v.iter();        // borrows: &i32
let into_iter = v.into_iter(); // owns: i32 (consumes v)
let mut_iter = v.iter_mut();   // mutably borrows: &mut i32

// Range is an iterator
for i in 1..=5 { print!("{} ", i); }  // 1 2 3 4 5

// String iteration
for c in "héllo".chars() { print!("{} ", c); }  // h é l l o
for b in "hi".bytes() { print!("{} ", b); }      // 104 105

Métodos Adaptadores (Lazy)

Métodos adaptadores transformam iteradores e retornam novos iteradores — eles são lazy, então encadear map().filter().map() cria zero coleções intermediárias. map aplica uma função a cada elemento. filter mantém elementos onde o predicado retorna true. take(n) para após n elementos (útil para iteradores infinitos). skip(n) descarta os primeiros n. flat_map mapeia e achata iteradores aninhados. enumerate emparelha cada elemento com seu índice. Nada é executado até um consumidor (collect, sum, for loop) conduzir o iterador.

rust
let nums = vec![1, 2, 3, 4, 5, 6];

// map: transform each element
let doubled: Vec<_> = nums.iter().map(|x| x * 2).collect();

// filter: keep elements matching predicate
let evens: Vec<_> = nums.iter().filter(|&&x| x % 2 == 0).collect();

// take / skip: limit or skip elements
let first3: Vec<_> = nums.iter().take(3).collect();     // [1,2,3]
let after2: Vec<_> = nums.iter().skip(2).collect();     // [3,4,5,6]

// flat_map: map then flatten one level
let words: Vec<_> = ["a b", "c"].iter()
    .flat_map(|s| s.split(' '))
    .collect();  // ["a","b","c"]

// enumerate: add index
for (i, v) in nums.iter().enumerate() {
    println!("{}: {}", i, v);
}

Métodos Consumidores

Métodos consumidores conduzem a cadeia de iterador lazy para realmente executar. collect() reúne resultados em qualquer coleção que implemente FromIterator (Vec, HashMap, String, etc.). sum/product/count/fold reduzem o iterador a um único valor. find/any/all fazem short-circuit — eles param assim que a resposta é conhecida, então são eficientes em iteradores infinitos. min/max retornam Option (None para iteradores vazios). A combinação de adaptadores lazy + um consumidor final significa que cadeias de iteradores são tão eficientes quanto loops escritos à mão após otimização.

rust
let nums = vec![1, 2, 3, 4, 5];

// collect: gather into a collection
let v: Vec<i32> = nums.iter().copied().collect();
let s: std::collections::HashSet<i32> = nums.iter().copied().collect();

// sum, product, count
let total: i32 = nums.iter().sum();        // 15
let prod: i32 = nums.iter().product();     // 120
let count = nums.iter().count();           // 5

// reduce / fold: accumulate into a single value
let max = nums.iter().copied().reduce(i32::max);  // Some(5)
let sum = nums.iter().fold(0, |acc, &x| acc + x); // 15

// find / any / all: short-circuiting
let first_even = nums.iter().find(|&&x| x % 2 == 0);  // Some(2)
let has_neg = nums.iter().any(|&x| *x < 0);  // false
let all_pos = nums.iter().all(|&x| *x > 0);  // true

// min / max
nums.iter().copied().min();  // Some(1)
nums.iter().copied().max();  // Some(5)

Iteradores Personalizados

Para criar um iterador personalizado, implemente o trait Iterator com um método next(). Uma vez feito, você ganha todos os 70+ métodos adaptadores e consumidores de graça. O iterador deve rastrear seu próprio estado (posição atual, etc.) e retornar None quando exausto. Implementar IntoIterator para seu tipo de coleção habilita a sintaxe de for-loop. Para acesso bidirecional ou aleatório, implemente também DoubleEndedIterator ou ExactSizeIterator. É assim que Vec, HashMap, Range e todas as coleções padrão fornecem iteração.

rust
// Build a custom iterator for a counter
struct Counter {
    current: usize,
    max: usize,
}

impl Counter {
    fn new(max: usize) -> Self {
        Counter { current: 0, max }
    }
}

impl Iterator for Counter {
    type Item = usize;
    fn next(&mut self) -> Option<Self::Item> {
        if self.current < self.max {
            let val = self.current;
            self.current += 1;
            Some(val)
        } else {
            None
        }
    }
}

// Now all iterator methods work!
let sum: usize = Counter::new(5).sum();  // 0+1+2+3+4 = 10
let doubled: Vec<_> = Counter::new(3).map(|x| x * 10).collect();

Iteradores Infinitos & Encadeados

Iteradores Rust podem ser infinitos — (1..) gera números naturais para sempre, repeat(x) repete infinitamente. Eles são seguros porque os adaptadores são lazy: take(n) limita o consumo. cycle() repete um iterador finito infinitamente. chain() concatena iteradores sequencialmente. zip() emparelha elementos posicionalmente (para no menor). peekable() permite olhar o próximo elemento sem consumi-lo — útil para parsers. O design lazy significa que iteradores infinitos não custam nada até serem consumidos, e o compilador otimiza cadeias em loops enxutos.

rust
use std::iter;

// Infinite iterators (use with take!)
let naturals = (1..).take(10);  // 1..10
let zeros = iter::repeat(0).take(5);  // [0,0,0,0,0]
let alternating = iter::repeat_with(|| rand::random::<u8>());

// cycle: repeat a finite iterator infinitely
let pattern = [1, 2, 3].iter().cycle().take(7);
// [1,2,3,1,2,3,1]

// chain: concatenate two iterators
let combined = [1, 2].iter().chain([3, 4].iter());
// [1,2,3,4]

// zip: pair elements from two iterators
let pairs: Vec<_> = [1, 2, 3].iter().zip(['a', 'b', 'c']).collect();
// [(1,'a'), (2,'b'), (3,'c')]

// peekable: look ahead without consuming
let mut iter = [1, 2, 3].iter().peekable();
if let Some(&&first) = iter.peek() { println!("{}", first); }
17

Tratamento de Erros Aprofundado

Combinadores de Result & Option

Combinadores permitem encadear operações falíveis sem expressões match aninhadas. map transforma o valor Ok, map_err transforma o erro. and_then encadeia operações que elas mesmas retornam Result (flatmap para erros). ok_or converte Option→Result. unwrap_or/unwrap_or_else/unwrap_or_default fornecem valores de fallback. Esses compõem elegantemente: parse().map().and_then().map_err() cria um pipeline onde cada passo pode falhar, e a primeira falha faz short-circuit. Prefira combinadores a unwrap() em código de produção.

rust
// Result combinators chain operations without match
fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse::<i32>().map(|n| n * 2)
}

// and_then: chain fallible operations
fn validate(n: i32) -> Result<i32, String> {
    if n > 0 { Ok(n) } else { Err("must be positive".into()) }
}
let result = "5".parse::<i32>().and_then(validate);  // Ok(5)

// map_err: transform the error type
let r = "abc".parse::<i32>()
    .map_err(|e| format!("Parse failed: {}", e));

// ok_or / ok_or_else: convert Option to Result
let opt: Option<i32> = None;
let val = opt.ok_or("missing value");  // Err("missing value")

// unwrap_or / unwrap_or_else / unwrap_or_default
let x: i32 = "abc".parse().unwrap_or(0);

Tipos de Erro Personalizados

Tipos de erro personalizados permitem representar falhas específicas de domínio. O padrão chave: implemente From para cada tipo de erro subjacente para que o operador ? converta automaticamente. Isso significa que você pode usar ? com std::io::Error, ParseIntError, etc. sem map_err explícito. Implementar Display torna o erro amigável ao usuário; Debug é para desenvolvedores. Erros baseados em enum são idiomáticos em Rust — eles são exaustivos (o compilador avisa sobre casos faltantes) e zero-cost (sem alocação de heap). Esta é a fundação antes de usar thiserror.

rust
#[derive(Debug)]
enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
    NotFound(String),
    Unauthorized,
}

// Implement From for automatic conversion with ?
impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}
impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self { AppError::Parse(e) }
}

impl std::fmt::Display for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO error: {}", e),
            AppError::Parse(e) => write!(f, "Parse error: {}", e),
            AppError::NotFound(s) => write!(f, "Not found: {}", s),
            AppError::Unauthorized => write!(f, "Unauthorized"),
        }
    }
}

// Now ? works for io::Error and ParseIntError automatically
fn read_config() -> Result<i32, AppError> {
    let s = std::fs::read_to_string("config.txt")?;  // io → AppError
    let n: i32 = s.trim().parse()?;                   // parse → AppError
    Ok(n)
}

O Trait Error & Box<dyn Error>

std::error::Error é o trait da biblioteca padrão para tipos de erro (requer Debug + Display). Box<dyn Error> é o tipo de erro mais simples — ele aceita qualquer erro via ? e é ótimo para prototipagem ou aplicações onde você não precisa tratar erros específicos programaticamente. A desvantagem: você perde o tipo de erro concreto, então matching em variantes específicas requer downcast_ref. Para bibliotecas, prefira um tipo de erro enum concreto (com thiserror). Para aplicações, anyhow é uma escolha melhor que Box<dyn Error> porque preserva backtraces e cadeias de erro.

rust
use std::error::Error;

// std::error::Error is the trait for all errors
// Requires: Debug + Display
fn do_something() -> Result<(), Box<dyn Error>> {
    let f = std::fs::read_to_string("file.txt")?;  // io::Error
    let n: i32 = f.parse()?;                         // ParseIntError
    println!("{}", n);
    Ok(())
}

// Box<dyn Error> is the quick-and-dirty error type
// — accepts any error via ?, but loses specific type info

// downcast to recover specific error type
match do_something() {
    Err(e) => {
        if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
            println!("IO: {}", io_err);
        }
    }
    _ => {}
}

Crate thiserror (Erros de Biblioteca)

thiserror é o crate padrão para tipos de erro de biblioteca. A macro #[derive(Error)] gera Display (a partir de #[error("...")]) e From (a partir de #[from]) automaticamente. #[from] faz ? converter o erro subjacente em sua variante de enum. Isso elimina boilerplate enquanto mantém um enum de erro fortemente tipado e exaustivo. Use thiserror para bibliotecas (onde chamadores precisam fazer match em erros específicos). O placeholder {0} insere o Display do erro interno; campos nomeados como {id} inserem campos da struct.

rust
use thiserror::Error;

// thiserror auto-generates Display and From impls
#[derive(Debug, Error)]
enum DataError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("parse failed: {0}")]
    Parse(#[from] std::num::ParseIntError),

    #[error("item {id} not found")]
    NotFound { id: u32 },

    #[error("invalid state: {msg}")]
    Invalid { msg: String },
}

// #[from] auto-implements From, so ? just works:
fn load() -> Result<i32, DataError> {
    let s = std::fs::read_to_string("data.txt")?;  // auto-converts
    let n: i32 = s.trim().parse()?;
    Ok(n)
}

Crate anyhow (Erros de Aplicação)

anyhow é o crate padrão para tratamento de erros de aplicação/binários. anyhow::Error envolve qualquer erro que implemente std::error::Error e adiciona contexto, backtraces e encadeamento de erros. context() anexa uma mensagem legível por humanos a cada passo falível, criando uma cadeia como 'Falha ao ler config: erro de IO: No such file'. Isso facilita muito a depuração — você vê exatamente qual passo falhou e por quê. Use anyhow para main() e código de aplicação onde você só precisa relatar erros, não fazer match neles. Use thiserror para bibliotecas onde chamadores precisam de erros tipados.

rust
use anyhow::{Context, Result, anyhow};

// anyhow::Result = Result<T, anyhow::Error>
fn load_config() -> Result<String> {
    let content = std::fs::read_to_string("config.toml")
        .context("Failed to read config.toml")?;  // add context
    Ok(content)
}

fn main() -> Result<()> {
    let config = load_config()?;
    if config.is_empty() {
        // bail! / anyhow! create errors with format! syntax
        return Err(anyhow!("config is empty"));
    }
    println!("{}", config);
    Ok(())
}

// Error chain: "Failed to read config.toml: No such file..."
// anyhow preserves the full chain and backtrace
18

Cargo & Crates Aprofundado

Estrutura do Cargo.toml

Cargo.toml é o manifesto para um projeto Rust. [package] descreve os metadados do crate. [dependencies] lista crates externos — strings de versão usam semver (^1.0 significa >=1.0, <2.0). features habilitam funcionalidade opcional (a feature derive do serde ativa #[derive(Serialize)]). [dev-dependencies] são apenas para testes/benchmarks. [features] definem flags de compilação condicional. [profile.release] controla configurações de otimização. O campo edition (2015/2018/2021) determina recursos da linguagem — sempre use a mais recente.

rust
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"
authors = ["You <[email protected]>"]
license = "MIT"
description = "A sample app"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", optional = true }
rand = "0.8"

[dev-dependencies]
criterion = "0.5"  # only for tests/benches

[features]
default = ["tokio"]
async-mode = ["dep:tokio"]

[[bin]]
name = "myapp"
path = "src/main.rs"

[profile.release]
opt-level = 3
lto = true
strip = true

Dependências & Feature Flags

Feature flags habilitam compilação condicional. Cada dependência pode expor features (ex.: derive do serde). default-features = false remove features padrão para reduzir o tamanho do binário. Dependências opcionais (optional = true) são compiladas apenas quando uma feature as habilita via dep:name. Features são aditivas — elas ligam coisas, nunca desligam. Isso garante unificação de features: se duas dependências habilitam features diferentes do serde, Cargo compila serde uma vez com a união de todas as features. Use #[cfg(feature = "x")] para compilar código condicionalmente.

rust
# Cargo.toml
[dependencies]
# Version requirements: ^1.2 (compatible), =1.2.3 (exact), >=1.0,<2.0 (range)
serde = "1.0"                    # ^1.0 (default: compatible)
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0", default-features = false }  # disable defaults

# Optional dependencies (enabled by a feature)
tokio = { version = "1", optional = true }

[features]
# "async" feature enables the optional tokio dep
async = ["dep:tokio"]
# Features can enable other features
full = ["async", "serde/derive"]

# In code:
# #[cfg(feature = "async")]
# fn run_async() { ... }

Workspaces (Projetos Multi-Crate)

Workspaces agrupam múltiplos crates relacionados que compartilham um Cargo.lock e diretório target. Isso acelera builds (cache de compilação compartilhado) e garante que todos os crates usem as mesmas versões de dependência. Membros podem depender uns dos outros via path = "../core". [workspace.dependencies] centraliza o gerenciamento de versões — crates membros as referenciam com { workspace = true }. O resolver = "2" (padrão na edition 2021) usa unificação de features por target, evitando alguns problemas de build. Use workspaces para monorepos, bibliotecas com múltiplos componentes ou projetos dividindo core/CLI/server.

rust
# Root Cargo.toml — workspace manifest
[workspace]
members = [
    "core",
    "cli",
    "server",
    "utils",
]
resolver = "2"

# Shared dependencies across all members
[workspace.dependencies]
serde = "1.0"
tokio = "1"

# In member crates (e.g., cli/Cargo.toml):
# [dependencies]
# serde = { workspace = true }
# core = { path = "../core" }  # local path dependency

Perfis de Build & Otimização

Perfis controlam como cargo constrói seu projeto. dev (padrão para cargo build) prioriza velocidade de compilação. release (cargo build --release) prioriza desempenho de runtime. Botões principais: opt-level (0-3, 's' para tamanho, 'z' para tamanho mínimo), lto (otimização em tempo de link entre fronteiras de crate), codegen-units (1 = melhor otimização mas compilação mais lenta), strip (remove símbolos para binários menores), panic = 'abort' (desativa unwinding, binário menor). Para produção, use lto = true, codegen-units = 1, strip = true. Perfis personalizados herdam de existentes.

rust
# Cargo.toml
[profile.dev]
opt-level = 0        # no optimization (fast compile)
debug = true         # include debug symbols
overflow-checks = true

[profile.release]
opt-level = 3        # max optimization
lto = "fat"          # link-time optimization across crates
codegen-units = 1    # single codegen unit (better opt, slower compile)
strip = true         # strip debug symbols from binary
panic = "abort"      # smaller binary, no unwinding

[profile.release.package."*"]
opt-level = 2  # optimize dependencies less than your code

# Custom profile
[profile.bench]
inherits = "release"
debug = true  # keep symbols for profiling

# Usage: cargo build --release --profile bench

Publicação & Documentação

Publicar para crates.io é permanente — versões não podem ser sobrescritas ou excluídas (apenas yanked, o que impede novos dependentes). Garanta que name, version, description, license e repository estejam definidos. cargo package valida o manifesto e mostra o que seria publicado. cargo doc gera documentação HTML a partir de comentários /// doc — doc tests (código em blocos ```) são compilados e executados por cargo test. Bons comentários doc com exemplos são tanto documentação quanto testes. Use #[doc(hidden)] para ocultar itens internos.

rust
# Before publishing:
# 1. Login (one-time)
# $ cargo login <token>  (token from crates.io)

# 2. Check the package
# $ cargo package        # creates .crate file, checks metadata
# $ cargo publish --dry-run

# 3. Publish
# $ cargo publish        # uploads to crates.io (irreversible!)

# Required fields in Cargo.toml for publishing:
# name, version, description, license, repository

# Documentation:
# $ cargo doc            # generate docs for your crate + deps
# $ cargo doc --open     # generate and open in browser
# $ cargo doc --no-deps  # only your crate

# Doc tests run automatically:
/// Adds two numbers.
/// 
/// # Examples
/// ```
/// let result = mycrate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
19

Trait Objects & Dispatch Dinâmico

Básico de dyn Trait

Trait objects (dyn Trait) habilitam polimorfismo de runtime: uma única variável pode conter diferentes tipos concretos implementando o mesmo trait. O compilador gera uma vtable (tabela de métodos virtuais) por tipo, e chamadas de método passam pela vtable (dispatch dinâmico). Isso tem um pequeno custo de runtime mas habilita coleções heterogêneas. Use trait objects quando o tipo concreto é desconhecido em tempo de compilação ou varia.

rust
trait Draw {
    fn draw(&self);
}

struct Circle { radius: f64 }
struct Square { side: f64 }

impl Draw for Circle {
    fn draw(&self) { println!("Circle r={}", self.radius); }
}
impl Draw for Square {
    fn draw(&self) { println!("Square s={}", self.side); }
}

// Trait object: type-erased, dynamic dispatch
let shapes: Vec<Box<dyn Draw>> = vec![
    Box::new(Circle { radius: 1.0 }),
    Box::new(Square { side: 2.0 }),
];
for s in &shapes { s.draw(); }

// Function taking trait object
fn render(shape: &dyn Draw) { shape.draw(); }

Segurança de Objeto (Object Safety)

Um trait é object-safe (pode ser usado como dyn Trait) apenas se: não tem métodos retornando Self, não tem métodos recebendo Self por valor, não tem métodos genéricos, e todos os métodos são dispatchable. Clone, Default e From NÃO são object-safe. Workarounds incluem usar retornos Box<Self> (padrão box_clone), dividir traits, ou usar dispatch estático com enums. O compilador relata claramente violações de object safety.

rust
// Object-safe trait (can be made into dyn)
trait Animal {
    fn sound(&self) -> String;
    fn name(&self) -> &str;
}

// NOT object-safe: returns Self
trait Clone {
    fn clone(&self) -> Self;  // Self is unknown for dyn
}

// NOT object-safe: takes Self by value
trait Add {
    fn add(&self, other: Self) -> Self;
}

// NOT object-safe: generic method
trait From {
    fn from<T>(t: T) -> Self;
}

// Fix: use where clauses or separate traits
trait AnimalSafe {
    fn sound(&self) -> String;
    fn box_clone(&self) -> Box<dyn AnimalSafe>;
}

Dispatch Estático vs Dinâmico

Dispatch estático (generics com trait bounds) monomorfiza: o compilador gera uma versão especializada por tipo concreto, habilitando inlining e desempenho máximo ao custo do tamanho do binário. Dispatch dinâmico (dyn Trait) usa lookups de vtable em runtime, binário menor mas chamadas mais lentas (impedindo inlining). Prefira dispatch estático para código crítico de desempenho; use dispatch dinâmico para coleções heterogêneas e sistemas de plugins.

rust
// Static dispatch (monomorphization)
fn max<T: Ord>(a: T, b: T) -> T {
    if a > b { a } else { b }
}
// Compiler generates max_i32, max_f64, etc.

// Dynamic dispatch (vtable lookup)
fn max_dyn(a: &dyn Ord, b: &dyn Ord) -> bool {
    // a > b  // Cannot use operators on dyn
    false
}

// Trait bound (static)
fn process<T: Display>(item: &T) {
    println!("{}", item);
}

// impl Trait (static, syntactic sugar)
fn process2(item: &impl Display) {
    println!("{}", item);
}

// dyn Trait (dynamic)
fn process3(item: &dyn Display) {
    println!("{}", item);
}

Trait Objects com Lifetimes

Trait objects podem carregar bounds de lifetime: Box<dyn Trait + 'a> significa que o trait object (e o tipo concreto por trás dele) deve viver pelo menos 'a. Por padrão, Box<dyn Trait> implica 'static. Ao armazenar trait objects que podem conter referências, adicione o lifetime explicitamente. A sintaxe + combina bounds de trait com lifetimes. Isso é comum em sistemas de plugins e manipuladores de eventos.

rust
trait Parser {
    fn parse(&self, input: &str) -> &str;
}

// Trait object with lifetime
fn make_parser() -> Box<dyn Parser> {
    Box::new(MyParser)
}

// Trait object holding references
struct Runner<'a> {
    parsers: Vec<Box<dyn Parser + 'a>>,
}

impl<'a> Runner<'a> {
    fn add(&mut self, p: Box<dyn Parser + 'a>) {
        self.parsers.push(p);
    }
}

// dyn Trait defaults to 'static when no lifetime given
fn static_parser() -> Box<dyn Parser> {
    Box::new(MyParser)
}

Downcasting & Any

O trait Any habilita verificação de tipo em runtime e downcasting. Any é automaticamente implementado para todos os tipos 'static. downcast_ref e downcast_mut retornam Option, permitindo recuperação segura de tipo. Isso é útil para sistemas de plugins, configurações dinâmicas e contêineres heterogêneos. Use Any com moderação — ele contorna o sistema de tipos. Prefira enums para alternativas conhecidas e generics para polimorfismo type-safe.

rust
use std::any::Any;

// Any enables runtime type identification
let x: Box<dyn Any> = Box::new(42_i32);

// Downcast to concrete type
if let Some(n) = x.downcast_ref::<i32>() {
    println!("Got i32: {}", n);
}

// Store heterogeneous values
let mut bag: Vec<Box<dyn Any>> = vec![
    Box::new(42_i32),
    Box::new("hello".to_string()),
    Box::new(3.14_f64),
];

for item in &bag {
    if let Some(s) = item.downcast_ref::<String>() {
        println!("String: {}", s);
    } else if let Some(n) = item.downcast_ref::<i32>() {
        println!("i32: {}", n);
    }
}
20

Macros Declarativas

Básico de macro_rules!

macro_rules! define macros declarativas que correspondem a padrões e se expandem para código. $( $x:expr ),* corresponde a uma lista separada por vírgulas de expressões, repetida zero ou mais vezes. O bloco $() ... * é repetido para cada correspondência. Macros são expandidas em tempo de compilação antes da verificação de tipos. Elas são higênicas: identificadores introduzidos pela macro não colidem com o código circundante. Use macros para reduzir boilerplate (vec!, println!, format!).

rust
macro_rules! vec_of {
    ( $( $x:expr ),* ) => {
        {
            let mut v = Vec::new();
            $(
                v.push($x);
            )*
            v
        }
    };
}

let nums = vec_of!(1, 2, 3, 4);
let strs = vec_of!("a", "b", "c");

// Multiple patterns
macro_rules! greet {
    () => { println!("Hello!") };
    ($name:expr) => { println!("Hello, {}!", $name) };
    ($name:expr, $greeting:expr) => {
        println!("{}, {}!", $greeting, $name)
    };
}
greet!();
greet!("Alice");
greet!("Bob", "Hi");

Tipos de Fragmento

Fragmentos de macro têm tipos específicos: expr (expressões), stmt (instruções), ty (tipos), pat (padrões), ident (identificadores), tt (árvores de tokens, o mais flexível), literal (literais), e mais. O tipo de fragmento determina o que a macro aceita e como ela faz parse. tt é o mais geral — qualquer sequência de tokens válida. Use o tipo mais específico possível para melhores mensagens de erro. O parser segue a regra Most-Recently-Added-Ambiguity.

rust
macro_rules! items {
    // $x:expr - expression (1 + 2, foo())
    // $x:stmt - statement (let x = 5;)
    // $x:ty - type (Vec<i32>, &str)
    // $x:pat - pattern (Some(x), (a, b))
    // $x:path - path (std::vec::Vec, Module::Type)
    // $x:ident - identifier (foo, Bar)
    // $x:literal - literal (42, "hello")
    // $x:tt - token tree (anything)
    // $x:block - block ({ ... })
    // $x:item - item (fn, struct)

    ($e:expr, $t:ty, $i:ident) => {
        let $i: $t = $e;
    };
}

items!(42, i32, my_num);
items!("hi", &str, greeting);

Padrões de Repetição

Repetição em macros: $(...)* corresponde a zero ou mais, $(...)+ corresponde a um ou mais, $(...)? corresponde a zero ou um. Separadores como vírgulas vão entre correspondências. Repetições aninhadas lidam com dados multidimensionais (matrizes, listas de listas). O bloco de expansão $() ... * repete para cada correspondência. Múltiplas variáveis na mesma repetição devem corresponder ao mesmo número de vezes. Use regras internas @prefix para padrões de acumulador.

rust
macro_rules! sum {
    // Zero or more: $(...)*
    ( $( $x:expr ),* ) => {
        {
            let mut total = 0;
            $(
                total += $x;
            )*
            total
        }
    };

    // One or more: $(...)+
    ( first $(, $x:expr )+ ) => {
        println!("First and more");
    };

    // With separator and count
    ( $( $x:expr ),+ $(; $sep:expr )? ) => {
        println!("List with optional separator");
    };
}

// Nested repetition
macro_rules! matrix {
    ( $( [ $( $x:expr ),* ] ),* ) => {
        vec![ vec![ $( $x ),* ],* ]
    };
}

Higiene & Exportação

A higiene de macro evita colisões de identificadores: variáveis introduzidas por uma macro vivem em seu próprio escopo e não capturam ou fazem shadow de variáveis do chamador. Use $crate para referir a itens no crate onde a macro é definida, garantindo que funcione após re-export. A convenção @prefix marca regras auxiliares internas que usuários não devem chamar diretamente. #[macro_export] publica a macro na raiz do crate.

rust
// Export at crate root
#[macro_export]
macro_rules! log {
    ($($arg:tt)*) => {
        // Hygienic: this T does not collide with caller's T
        let _ts: std::time::Instant = std::time::Instant::now();
        eprintln!("[{}] {}", "LOG", format!($($arg)*));
    };
}

// Use $crate to refer to crate items (works after re-export)
#[macro_export]
macro_rules! make_error {
    ($msg:expr) => {
        $crate::Error::new($msg)
    };
}

// Internal helper rule (convention: @ prefix)
macro_rules! count {
    (@count $x:expr, $($rest:expr),*) => { 1 + count!(@count $($rest),*) };
    (@count $x:expr) => { 1 };
    () => { 0 };
}

Padrões Comuns de Macro

Macros se destacam em construir DSLs e reduzir boilerplate. Padrões comuns: DSLs de builder (html!, sql!), asserções de teste (assert_approx!), configuração (config!) e geração de código (macros semelhantes a derive). Macros são higênicas e em tempo de compilação, então não têm custo de runtime. Limitações: sem profundidade de recursão além de 64, mensagens de erro complexas e dificuldade com parsing não trivial. Para metaprogramação complexa, use macros procedurais.

rust
// 1. Builder DSL
macro_rules! html {
    ($tag:ident { $($body:tt)* }) => {
        format!("<{}>{}</{}>", stringify!($tag), html_inner!($($body)*), stringify!($tag))
    };
}

// 2. Test assertion
macro_rules! assert_approx {
    ($a:expr, $b:expr, $eps:expr) => {
        assert!(($a - $b).abs() < $eps, "{} != {} within {}", $a, $b, $eps);
    };
}

// 3. Configuration
macro_rules! config {
    ( $( $key:ident : $val:expr ),* ) => {
        {
            let mut m = std::collections::HashMap::new();
            $(
                m.insert(stringify!($key).to_string(), $val.to_string());
            )*
            m
        }
    };
}

let cfg = config! { host: "localhost", port: 8080 };
21

Cargo & Workspaces

Configuração de Workspace

Workspaces agrupam múltiplos crates que compartilham dependências e um diretório target. Membros são listados explicitamente ou via globs. [workspace.package] define metadados de pacote compartilhados, herdados com .workspace = true. [workspace.dependencies] centraliza versões de dependência, garantindo que todos os crates usem a mesma versão. Isso evita conflitos de versão e acelera builds (único Cargo.lock). Use workspaces para projetos multi-crate como CLI + biblioteca + servidor.

rust
# Root Cargo.toml
[workspace]
members = ["crates/*", "cli", "server"]
resolver = "2"

[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Team <[email protected]>"]
license = "MIT"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"

# Member crate: crates/mylib/Cargo.toml
[package]
name = "mylib"
version.workspace = true
edition.workspace = true

[dependencies]
serde.workspace = true
tokio.workspace = true

Perfis de Build

Perfis controlam configurações de compilação. dev prioriza compilação rápida (opt-level 0, símbolos de depuração). release maximiza desempenho de runtime (opt-level 3, LTO, única codegen unit). LTO (Link-Time Optimization) habilita inlining cross-crate. panic = "abort" produz binários menores mas desativa unwinding. Overrides por pacote (profile.dev.package."*") otimizam dependências mesmo em dev. Perfis personalizados herdam de existentes.

rust
# Cargo.toml
[profile.dev]
opt-level = 0        # No optimization (fast compile)
debug = true         # Include debug symbols
overflow-checks = true

[profile.release]
opt-level = 3        # Max optimization
debug = false
lto = "fat"          # Link-time optimization
codegen-units = 1    # Single unit (better opt, slower compile)
panic = "abort"      # Smaller binary, no unwinding
strip = true         # Strip symbols

[profile.dev.package."*"]
opt-level = 2        # Optimize dependencies in dev

# Custom profile
[profile.bench]
inherits = "release"
debug = true

# Usage: cargo build --release, cargo build --profile=bench

Features & Compilação Condicional

Features habilitam compilação condicional. Dependências opcionais tornam-se features automaticamente. cfg(feature = "...") faz gate de código por feature. O conjunto de features padrão é habilitado a menos que --no-default-features seja passado. Features devem ser aditivas (habilitar mais, não menos). Use features para reduzir o tamanho do binário, suportar múltiplos backends ou fazer gate de código experimental. Combine com cfg_attr para macros derive condicionais. Evite features mutuamente exclusivas.

rust
# Cargo.toml
[features]
default = ["json"]
json = ["serde_json"]
yaml = ["serde_yaml"]
async-runtime = ["tokio"]

[dependencies]
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
serde_yaml = { version = "0.9", optional = true }
tokio = { version = "1", optional = true, features = ["full"] }

# Code: conditional compilation
#[cfg(feature = "json")]
pub fn parse_json(s: &str) -> Result<Value, Error> {
    serde_json::from_str(s)
}

#[cfg(not(feature = "json"))]
pub fn parse_json(_: &str) -> Result<Value, Error> {
    Err(Error::FeatureNotEnabled)
}

Build Scripts (build.rs)

build.rs executa antes da compilação, habilitando geração de código, incorporação de ambiente e linking de bibliotecas C. Diretivas cargo:rerun-if-* controlam quando o script reexecuta. Gere código com macros println!, então inclua-o com include! no seu crate. Usos comuns: incorporar informações de versão, gerar bindings (bindgen), compilar esquemas protobuf/SQL e linking de bibliotecas de sistema. Mantenha build scripts rápidos — eles executam a cada build.

rust
// build.rs: runs before compilation
use std::env;
use std::fs;
use std::path::Path;

fn main() {
    // Tell Cargo to rerun if env changes
    println!("cargo:rerun-if-env-changed=DATABASE_URL");

    let out_dir = env::var("OUT_DIR").unwrap();
    let dest = Path::new(&out_dir).join("config.rs");

    let db_url = env::var("DATABASE_URL")
        .unwrap_or_else(|_| "sqlite://default.db".to_string());

    // Generate Rust code at build time
    fs::write(&dest, format!(
        "pub const DATABASE_URL: &str = \"{}\";",
        db_url
    )).unwrap();

    // Link a C library
    println!("cargo:rustc-link-lib=static=mylib");
    println!("cargo:rustc-link-search=native=/usr/local/lib");
}

// In code: include generated file
include!(concat!(env!("OUT_DIR"), "/config.rs"));

Publicação & Documentação

cargo publish faz upload de um crate para crates.io. Use --dry-run para verificar antes de publicar. cargo doc gera documentação HTML a partir de comentários doc (///). Blocos de código em comentários doc são testados com cargo test --doc. Inclua seções Examples, Panics e Errors. Metadados (description, repository, keywords) melhoram a descoberta. Uma vez publicado, uma versão não pode ser reutilizada ou excluída — use yank para impedir que novos projetos dependam dela.

rust
# Publish to crates.io
cargo login <token>     # One-time authentication
cargo publish           # Publish current crate

# Before publishing:
cargo publish --dry-run # Verify package contents
cargo package           # Inspect the .crate file

# Documentation
cargo doc               # Generate docs
cargo doc --open        # Generate and open
cargo doc --no-deps     # Only this crate

# In code: doc comments
/// Adds two numbers.
///
/// # Examples
/// ```
/// let result = mycrate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }

# README and metadata in Cargo.toml
[package]
description = "A short description"
repository = "https://github.com/user/repo"
readme = "README.md"
keywords = ["parser", "cli"]
categories = ["command-line-utilities"]
22

Macros Procedurais

Tipos de Macro

Macros procedurais geram código Rust em tempo de compilação, operando em fluxos de tokens. Três tipos: semelhante a função (custom!()), derive (#[derive(Custom)]) e atributo (#[custom]). Elas exigem um crate separado com proc-macro = true. O crate syn faz parse da sintaxe Rust, quote gera código, e proc-macro2 habilita testes. Proc-macros são poderosas mas complexas — use-as para macros derive, DSLs e geração de código que macros declarativas não conseguem tratar.

rust
// Three types of procedural macros:
// 1. Function-like: my_macro!(...)
// 2. Derive: #[derive(MyMacro)]
// 3. Attribute: #[my_macro]

// Cargo.toml for a proc-macro crate
// [lib]
// proc-macro = true

// [dependencies]
// syn = { version = "2", features = ["full"] }
// quote = "1"
// proc-macro2 = "1"

use proc_macro::TokenStream;

#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
    "fn answer() -> i32 { 42 }".parse().unwrap()
}

// Usage: make_answer!();
// Generates: fn answer() -> i32 { 42 }

Macro Derive

Macros derive adicionam implementações de trait a tipos anotados com #[derive(MyMacro)]. syn faz parse da entrada em uma AST DeriveInput. quote! gera código com interpolação # para variáveis. Atributos auxiliares (attributes(hello)) permitem customização em campos ou variantes. Macros derive comuns: Debug, Clone, Serialize, Deserialize. O código gerado é anexado ao módulo, então não pode modificar o tipo original.

rust
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let expanded = quote! {
        impl HelloMacro for #name {
            fn hello() {
                println!("Hello from {}!", stringify!(#name));
            }
        }
    };

    expanded.into()
}

// Usage:
// #[derive(HelloMacro)]
// struct Pancakes;
// Pancakes::hello();  // "Hello from Pancakes!"

// Helper attributes
#[proc_macro_derive(HelloMacro, attributes(hello))]
pub fn hello_with_attr(input: TokenStream) -> TokenStream { /* ... */ }

Macro de Atributo

Macros de atributo (#[my_attr]) transformam o item que anotam, potencialmente substituindo-o inteiramente. Elas recebem tanto os argumentos do atributo quanto o item anotado. Usos comuns: logging, caching, wrappers async (#[tokio::main]) e roteamento (#[get("/path")]). Macros de atributo podem mudar a assinatura do item, adicionar código ou gerar itens adicionais. Elas são mais flexíveis que macros derive mas mais difíceis de usar corretamente.

rust
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn log_calls(attr: TokenStream, item: TokenStream) -> TokenStream {
    let attr_args = syn::parse_macro_input!(attr as syn::AttributeArgs);
    let input_fn = parse_macro_input!(item as ItemFn);

    let fn_name = &input_fn.sig.ident;
    let fn_block = &input_fn.block;

    let expanded = quote! {
        fn #fn_name() {
            println!("Calling {}", stringify!(#fn_name));
            let __result = (|| #fn_block)();
            println!("Finished {}", stringify!(#fn_name));
            __result
        }
    };

    expanded.into()
}

// Usage:
// #[log_calls]
// fn my_function() -> i32 { 42 }

Macro Semelhante a Função

Macros procedurais semelhantes a funções (my_macro!()) aceitam fluxos de tokens arbitrários, habilitando DSLs personalizadas. Implemente Parse para definir a sintaxe aceita. A macro pode validar, transformar ou gerar código com base na entrada. Usos comuns: consultas SQL (sqlx), templates HTML (maud) e DSLs de configuração. Ao contrário de macros declarativas, proc-macros podem fazer parse de sintaxe complexa e realizar computação arbitrária em tempo de compilação. Mantenha-as rápidas para evitar builds lentos.

rust
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse::Parse, parse::ParseStream, parse_macro_input};

// Custom syntax: sql!(SELECT * FROM users WHERE id = $1)
struct SqlQuery {
    query: String,
}

impl Parse for SqlQuery {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let query = input.to_string();
        Ok(SqlQuery { query })
    }
}

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let SqlQuery { query } = parse_macro_input!(input as SqlQuery);

    let expanded = quote! {
        {
            static QUERY: &str = #query;
            // Compile-time SQL validation could go here
            QUERY
        }
    };

    expanded.into()
}

Testes & Depuração

Testando proc-macros: trybuild executa testes de UI comparando a saída do compilador (mensagens de sucesso ou erro) com arquivos esperados. Para macros derive, teste que o código gerado compila e se comporta corretamente. Depure com eprintln! (impresso durante a compilação) ou cargo expand (mostra a saída de macro expandida). O desenvolvimento de macros é iterativo: escreva a macro, use cargo expand para inspecionar a saída, corrija problemas. Documente a sintaxe da macro e os recursos suportados claramente.

rust
// Cargo.toml
// [dev-dependencies]
// trybuild = "1"

// tests/ui/my_macro.rs - test file
// #[derive(MyMacro)]
// struct Foo;
// fn main() { Foo::hello(); }

// tests/ui/my_macro.stderr - expected error
// error: ...

// Test runner
#[test]
fn ui() {
    let t = trybuild::TestCases::new();
    t.pass("tests/ui/pass_*.rs");
    t.compile_fail("tests/ui/fail_*.rs");
}

// Debugging with eprintln
#[proc_macro_derive(Debug)]
pub fn debug_derive(input: TokenStream) -> TokenStream {
    eprintln!("Input tokens: {}", input);
    let ast = syn::parse2(input.clone().into()).unwrap();
    eprintln!("Parsed AST: {:#?}", ast);
    TokenStream::new()
}
23

Macros

macro_rules!

macro_rules! define macros declarativas. $x é uma captura, expr corresponde a expressões. $(...)* repete. Macros são expandidas em tempo de compilação. Útil para reduzir boilerplate. A macro padrão vec! funciona de forma similar.

rust
macro_rules! vec_of {
    ($($x:expr),*) => {{
        let mut v = Vec::new();
        $(v.push($x);)*
        v
    }};
}
let nums = vec_of!(1, 2, 3);

Macros Procedurais

Macros procedurais geram código em tempo de compilação. Três tipos: derive (#[derive(Debug)]), atributo (#[my_attr]), semelhante a função (my_macro!). Mais poderosas que macro_rules! mas exigem um crate separado. Usadas por serde, tokio e diesel.

rust
// In a separate crate with proc-macro = true
use proc_macro::TokenStream;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // Generate impl HelloMacro for the type
    // Returns new TokenStream
}

Macros Comuns

Macros integradas: println!/format! para saída, vec! para vetores, assert!/assert_eq! para testes, dbg! para depuração, todo!/unreachable! para fluxo de controle. Todas são baseadas em macro_rules!. dbg! retorna o valor para encadeamento.

rust
println!("Hello, {}!", "world");
format!("x = {}", 42);
vec![1, 2, 3];
assert!(1 + 1 == 2);
assert_eq!(2 + 2, 4);
dbg!(some_variable);  // Debug print
todo!("Not implemented");
unreachable!();

Higiene de Macro

Macros Rust são higênicas: identificadores introduzidos pela macro não conflitam com identificadores no escopo de chamada. Isso evita bugs sutis. O temp dentro da macro é diferente do temp externo. Macros declarativas são sempre higênicas.

rust
macro_rules! swap {
    ($a:expr, $b:expr) => {
        let temp = $a;
        $a = $b;
        $b = temp;
    };
}
// temp is hygienic: does not conflict with outer temp
let mut temp = 1;
let mut x = 2;
swap!(temp, x);  // Works correctly

Repetição

Repetição em macros: $(...)* corresponde a zero ou mais, $(...)+ a um ou mais. O separador (vírgula) pode ser especificado. $x captura cada valor. Útil para funções semelhantes a variádicas. A macro padrão println! usa isso para múltiplos argumentos.

rust
macro_rules! sum {
    ($($x:expr),*) => {
        0 $(+ $x)*
    };
}
let total = sum!(1, 2, 3, 4);  // 10
// $(...)* zero or more, $(...)+ one or more
// $(...),? optional trailing comma
24

Async Aprofundado

async/await

async fn retorna um Future. .await suspende até o future estar pronto. Futures são lazy: nada é executado até ser awaited. O compilador transforma async fn em uma máquina de estados. Use o runtime tokio ou async-std para executar.

rust
async fn fetch_data() -> String {
    // Simulate async work
    String::from("data")
}
async fn process() {
    let data = fetch_data().await;
    println!("{}", data);
}

Runtime Tokio

tokio::main habilita async main. spawn cria uma task (como uma green thread). join! espera múltiplos futures concorrentemente. Tokio fornece I/O, timers e agendamento. O runtime async mais popular em Rust.

rust
#[tokio::main]
async fn main() {
    let task1 = tokio::spawn(async { work1().await });
    let task2 = tokio::spawn(async { work2().await });
    let (r1, r2) = tokio::join!(task1, task2);
}

Channels (async)

Canais mpsc (multi-producer, single-consumer) habilitam comunicação async. send/recv são async. O canal tem um buffer (32 mensagens). Quando todos os senders são dropados, recv retorna None. Útil para padrões producer-consumer.

rust
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32);
    tokio::spawn(async move {
        tx.send("hello").await.unwrap();
    });
    while let Some(msg) = rx.recv().await {
        println!("{}", msg);
    }
}

Select

select! espera o primeiro de múltiplos futures completar. Outros futures são dropados. Útil para timeouts e operações de corrida. O padrão é comum em servidores de rede. Cada branch pode ter um padrão de guarda.

rust
tokio::select! {
    result = task1 => {
        println!("Task1 done: {:?}", result);
    }
    result = task2 => {
        println!("Task2 done: {:?}", result);
    }
    _ = tokio::time::sleep(Duration::from_secs(5)) => {
        println!("Timeout");
    }
}

Stream

Stream é o equivalente async de Iterator. next().await obtém o próximo item. StreamExt fornece map, filter, for_each. Útil para processar pedaços de dados da rede ou arquivos. O crate async-stream simplifica a criação de streams.

rust
use tokio_stream::{self as stream, StreamExt};
let mut stream = stream::iter(vec![1, 2, 3]);
while let Some(item) = stream.next().await {
    println!("{}", item);
}
// Map, filter like iterators but async
stream.map(|x| x * 2).filter(|x| *x > 2).for_each(|x| async move {
    println!("{}", x);
}).await;
25

Cargo & Crates

Cargo.toml

Cargo.toml é o arquivo de manifesto. [package] define metadados. [dependencies] lista crates externos. Features habilitam funcionalidade opcional. [dev-dependencies] são apenas para testes. Edition 2021 é a mais recente estável. Versões usam semver.

rust
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

[dev-dependencies]
pretty_assertions = "1"

Comandos Cargo

cargo new cria um projeto binário (--lib para biblioteca). build compila para target/. --release habilita otimizações. check é mais rápido que build (sem codegen). clippy captura erros comuns. fmt formata código. doc gera documentação HTML. add insere uma dependência.

rust
cargo new myapp        # Create new project
cargo build            # Compile
cargo build --release  # Optimized build
cargo run              # Build and run
cargo test             # Run tests
cargo check            # Fast type-check
cargo fmt              # Format code
cargo clippy           # Lint
cargo doc --open       # Generate docs
cargo add serde        # Add dependency

Workspaces

Workspaces agrupam múltiplos crates que compartilham um diretório target e Cargo.lock. Membros são crates individuais. [workspace.dependencies] centraliza versões de dependência. Cada crate as referencia com workspace = true. Builds mais rápidos devido à compilação compartilhada. Usado por grandes projetos como rust-analyzer.

rust
# Root Cargo.toml
[workspace]
members = ["crate-a", "crate-b"]

# Shared dependencies
[workspace.dependencies]
serde = "1.0"

# In crate-a/Cargo.toml
[dependencies]
serde = { workspace = true }

Features

Features habilitam compilação condicional. Features padrão são habilitadas a menos que --no-default-features. cfg(feature = ...) faz gate de código. A sintaxe dep: em dependências evita unificação de features. Útil para funcionalidade opcional e código específico de plataforma. Crates podem expor features a consumidores.

rust
# Cargo.toml
[features]
default = ["csv"]
csv = ["dep:csv-parse"]
json = ["dep:serde_json"]

# Conditional compilation
#[cfg(feature = "csv")]
pub fn parse_csv() { /* ... */ }

Publicação

crates.io é o registro de pacotes do Rust. cargo login autentica. --dry-run captura problemas. Uma vez publicado, uma versão não pode ser republicada (yank apenas oculta da busca). Siga semver: patch para correções, minor para features, major para mudanças breaking. README e licença são obrigatórios.

rust
# Login (one time)
cargo login <token>

# Check before publishing
cargo publish --dry-run

# Publish to crates.io
cargo publish

# Version bumping
cargo bump patch  # 0.1.0 -> 0.1.1
cargo bump minor  # 0.1.0 -> 0.2.0
cargo bump major  # 0.1.0 -> 1.0.0
26

Testando Rust

Testes Unitários

Testes vivem em um módulo #[cfg(test)]. use super::* importa o pai. #[test] marca funções de teste. assert_eq! verifica igualdade. #[should_panic] espera um panic. Testes executam com cargo test. Testes unitários são colocalizados com o código. O atributo cfg(test) garante que testes não sejam compilados em builds de release.

rust
pub fn add(a: i32, b: i32) -> i32 { a + b }

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
    #[test]
    #[should_panic]
    fn test_panic() {
        panic!("expected");
    }
}

Testes de Integração

Testes de integração vivem no diretório tests/. Cada arquivo é compilado como um crate separado. Eles só podem testar a API pública. Úteis para testes end-to-end. Execute testes específicos com --test <name>. Testes de integração são mais lentos para compilar mas testam a interface real.

rust
// tests/integration_test.rs
use myapp::add;

#[test]
fn test_add_integration() {
    assert_eq!(add(2, 3), 5);
}
// Run: cargo test --test integration_test
// Each file in tests/ is a separate crate

Organização de Testes

#[ignore] pula um teste a menos que --ignored seja passado. Filtre testes por padrão de nome. --nocapture mostra saída de println!. Testes executam em paralelo por padrão. Use --test-threads=1 para sequencial. Harnesses personalizados podem substituir o runner de teste padrão. Útil para benchmarks e property tests.

rust
#[test]
fn it_works() { /* ... */ }

// Custom test harness
#[test]
#[ignore = "slow test"]
fn slow_test() { /* ... */ }

// Run only ignored tests
cargo test -- --ignored

// Filter by name
cargo test test_add

// Show output
cargo test -- --nocapture

Asserções

assert! verifica um booleano. assert_eq!/assert_ne! comparam valores com saída de debug em falha. Mensagens personalizadas ajudam na depuração. Para ponto flutuante, use o crate approx. Para igualdade parcial, implemente PartialEq. A saída de debug mostra ambos os valores quando a asserção falha.

rust
assert!(true);                          // Boolean
assert_eq!(2 + 2, 4);                   // Equality
assert_ne!(3, 4);                       // Inequality
assert!(x > 0, "x must be positive");   // Custom message
assert_eq!(a, b, "got {}, expected {}", a, b);
// Debug output on failure
assert_eq!(vec![1, 2], vec![1, 2]);

Property Testing

proptest gera entradas aleatórias para encontrar casos falhos. Strategies (a in range) definem geradores de entrada. prop_assert! relata falhas com contraexemplos mínimos. Shrinking encontra a menor entrada falha. Melhor que testes escritos à mão para casos extremos. Similar ao QuickCheck em Haskell.

rust
// Cargo.toml: proptest = "1"
use proptest::prelude::*;

proptest! {
    #[test]
    fn test_add_commutative(a in -1000..1000, b in -1000..1000) {
        prop_assert_eq!(add(a, b), add(b, a));
    }
    #[test]
    fn test_string_len(s in ".{0,100}") {
        prop_assert!(s.len() <= 100);
    }
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.