Rust Cheatsheet

Referência rápida da linguagem Rust. Todos os conceitos essenciais em uma página.

Rust Cheatsheet - Referência Rápida

Guia de consulta rápida com os principais conceitos da linguagem Rust. Use Ctrl+F para buscar o que precisa.


Variáveis e Tipos

Declaração de Variáveis

let x = 5;              // imutável (padrão)
let mut y = 10;          // mutável
let z: i32 = 42;         // com anotação de tipo
const MAX: u32 = 100;    // constante (sempre tipada, UPPER_CASE)
static NOME: &str = "Rust"; // variável estática (tempo de vida 'static)

Tipos Primitivos

TipoDescriçãoExemplo
i8, i16, i32, i64, i128Inteiro com sinallet x: i32 = -42;
u8, u16, u32, u64, u128Inteiro sem sinallet x: u32 = 42;
isize, usizeTamanho do ponteirolet i: usize = 0;
f32, f64Ponto flutuantelet pi: f64 = 3.14;
boolBooleanolet ativo = true;
charCaractere Unicodelet c = 'á';
()Unit (vazio)let vazio = ();
[T; N]Array de tamanho fixolet arr = [1, 2, 3];
(T, U, ..)Tuplalet t = (1, "oi", true);

String vs &str

// &str - string slice, referência imutável, tamanho fixo
let s1: &str = "olá mundo";          // literal, alocada no binário
let s2: &str = &outra_string[0..3];  // slice de uma String

// String - tipo heap, mutável, tamanho dinâmico
let s3: String = String::from("olá");
let s4: String = "olá".to_string();
let s5: String = format!("{} {}", "olá", "mundo");

// Conversões
let para_string: String = s1.to_string();   // &str -> String
let para_str: &str = &s3;                   // String -> &str (deref coercion)
let para_str2: &str = s3.as_str();          // String -> &str (explícito)

Shadowing e Conversão de Tipos

let x = 5;
let x = x + 1;          // shadowing: novo binding com mesmo nome
let x = "agora é texto"; // pode mudar o tipo com shadowing

// Casting
let inteiro = 42i32;
let flutuante = inteiro as f64;
let byte = 255u8;

// Parsing
let numero: i32 = "42".parse().unwrap();
let numero2 = "42".parse::<i32>().unwrap(); // turbofish

Funções e Closures

Funções

// Função simples
fn somar(a: i32, b: i32) -> i32 {
    a + b  // sem ponto-e-vírgula = retorno implícito
}

// Sem retorno (retorna ())
fn imprimir(msg: &str) {
    println!("{}", msg);
}

// Retorno antecipado
fn dividir(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        return None;  // retorno explícito com 'return'
    }
    Some(a / b)
}

// Múltiplos retornos via tupla
fn min_max(lista: &[i32]) -> (i32, i32) {
    let min = *lista.iter().min().unwrap();
    let max = *lista.iter().max().unwrap();
    (min, max)
}

// Parâmetros genéricos
fn maior<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

Closures

// Closure sem parâmetros
let saudar = || println!("Olá!");

// Closure com parâmetros
let somar = |a: i32, b: i32| a + b;

// Closure com corpo em bloco
let processar = |x: i32| {
    let resultado = x * 2;
    resultado + 1
};

// Closure capturando variável do escopo
let fator = 3;
let multiplicar = |x| x * fator;      // captura 'fator' por referência
let mover = move |x| x * fator;       // captura 'fator' por valor (move)

// Closure como parâmetro de função
fn aplicar<F: Fn(i32) -> i32>(f: F, valor: i32) -> i32 {
    f(valor)
}
let resultado = aplicar(|x| x * 2, 5); // 10

Traits de Closure

TraitDescriçãoCaptura
FnPode ser chamada múltiplas vezes&self (referência imutável)
FnMutPode ser chamada múltiplas vezes, pode mutar estado&mut self (referência mutável)
FnOncePode ser chamada uma única vezself (toma ownership)
fn executar_fn(f: impl Fn())          { f(); f(); }
fn executar_fn_mut(mut f: impl FnMut()) { f(); f(); }
fn executar_fn_once(f: impl FnOnce())   { f(); }

// Higher-order: função que retorna closure
fn criar_somador(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}
let soma5 = criar_somador(5);
println!("{}", soma5(10)); // 15

Controle de Fluxo

if / else

let x = 5;

if x > 10 {
    println!("maior que 10");
} else if x > 0 {
    println!("positivo");
} else {
    println!("zero ou negativo");
}

// if como expressão
let descricao = if x > 0 { "positivo" } else { "não positivo" };

// if let - desestruturação condicional
let valor: Option<i32> = Some(42);
if let Some(v) = valor {
    println!("Valor: {}", v);
}

match

let x = 3;

match x {
    1 => println!("um"),
    2 | 3 => println!("dois ou três"),    // múltiplos padrões
    4..=10 => println!("entre 4 e 10"),   // range inclusivo
    n if n < 0 => println!("negativo"),   // guard
    _ => println!("outro"),               // wildcard (padrão)
}

// match como expressão
let texto = match x {
    1 => "um",
    2 => "dois",
    _ => "outro",
};

// match com desestruturação
let ponto = (3, -5);
match ponto {
    (0, 0) => println!("origem"),
    (x, 0) => println!("no eixo x em {}", x),
    (0, y) => println!("no eixo y em {}", y),
    (x, y) => println!("ponto ({}, {})", x, y),
}

// match com enum
enum Comando {
    Sair,
    Mover { x: i32, y: i32 },
    Escrever(String),
    Cor(u8, u8, u8),
}

let cmd = Comando::Mover { x: 10, y: 20 };
match cmd {
    Comando::Sair => println!("saindo"),
    Comando::Mover { x, y } => println!("mover para ({}, {})", x, y),
    Comando::Escrever(texto) => println!("{}", texto),
    Comando::Cor(r, g, b) => println!("cor: {}, {}, {}", r, g, b),
}

Loops

// loop infinito
let mut contador = 0;
let resultado = loop {
    contador += 1;
    if contador == 10 {
        break contador * 2;  // loop pode retornar valor com break
    }
};

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

// while let
let mut pilha = vec![1, 2, 3];
while let Some(topo) = pilha.pop() {
    println!("{}", topo);
}

// for - iteração sobre range
for i in 0..5 {
    println!("{}", i);     // 0, 1, 2, 3, 4
}
for i in 0..=5 {
    println!("{}", i);     // 0, 1, 2, 3, 4, 5
}

// for - iteração sobre coleção
let nomes = vec!["Ana", "Bia", "Carlos"];
for nome in &nomes {               // referência (não consome)
    println!("{}", nome);
}
for (i, nome) in nomes.iter().enumerate() {
    println!("{}: {}", i, nome);   // com índice
}

// labels para loops aninhados
'externo: for i in 0..5 {
    for j in 0..5 {
        if i + j > 4 {
            break 'externo;        // sai do loop externo
        }
    }
}

Ownership e Borrowing

Regras de Ownership

  1. Cada valor tem exatamente um dono (owner).
  2. Quando o dono sai de escopo, o valor é descartado (drop).
  3. Pode haver apenas uma das seguintes situações por vez:
    • Uma referência mutável (&mut T)
    • Qualquer número de referências imutáveis (&T)
// Move (tipos que não implementam Copy)
let s1 = String::from("olá");
let s2 = s1;           // s1 foi MOVIDO para s2
// println!("{}", s1);  // ERRO: s1 não é mais válido

// Clone (cópia explícita)
let s3 = String::from("olá");
let s4 = s3.clone();   // cópia profunda
println!("{} {}", s3, s4); // ambos válidos

// Copy (tipos primitivos são copiados, não movidos)
let x = 5;
let y = x;              // cópia (i32 implementa Copy)
println!("{} {}", x, y); // ambos válidos

Borrowing (Empréstimo)

fn calcular_tamanho(s: &String) -> usize {
    s.len()
    // s é devolvido automaticamente (é apenas empréstimo)
}

let s = String::from("olá");
let tam = calcular_tamanho(&s);  // empréstimo imutável
println!("{} tem {} bytes", s, tam); // s ainda é válido

// Referência mutável
fn adicionar_texto(s: &mut String) {
    s.push_str(" mundo");
}

let mut s = String::from("olá");
adicionar_texto(&mut s);
println!("{}", s); // "olá mundo"

// Regra: não pode ter &mut junto com & ao mesmo tempo
let mut dados = String::from("abc");
let r1 = &dados;      // ok: referência imutável
let r2 = &dados;      // ok: outra referência imutável
// let r3 = &mut dados; // ERRO: não pode emprestar como mutável
println!("{} {}", r1, r2);
// r1 e r2 não são mais usados depois daqui (NLL)
let r3 = &mut dados;  // ok agora: r1 e r2 já saíram de escopo (NLL)
r3.push_str("d");

Lifetimes (Tempos de Vida)

// Lifetime explícito - garante que a referência retornada vive o suficiente
fn mais_longo<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Lifetime em struct
struct Trecho<'a> {
    texto: &'a str,
}

impl<'a> Trecho<'a> {
    fn novo(texto: &'a str) -> Self {
        Trecho { texto }
    }
}

// Lifetime estático - vive durante toda a execução do programa
let s: &'static str = "Eu vivo para sempre";

// Regras de elisão (o compilador infere automaticamente):
// 1. Cada parâmetro de referência recebe seu próprio lifetime
// 2. Se há exatamente um lifetime de entrada, ele é usado na saída
// 3. Se um dos parâmetros é &self ou &mut self, seu lifetime é usado na saída

Structs e Enums

Structs

// Struct clássica
struct Usuario {
    nome: String,
    email: String,
    idade: u32,
    ativo: bool,
}

// Instanciação
let usuario = Usuario {
    nome: String::from("Ana"),
    email: String::from("ana@email.com"),
    idade: 30,
    ativo: true,
};

// Field init shorthand (quando variável tem mesmo nome do campo)
let nome = String::from("Bia");
let email = String::from("bia@email.com");
let usuario2 = Usuario { nome, email, idade: 25, ativo: true };

// Struct update syntax
let usuario3 = Usuario {
    nome: String::from("Carlos"),
    ..usuario2  // pega os demais campos de usuario2
};

// Tuple struct
struct Cor(u8, u8, u8);
struct Ponto(f64, f64, f64);
let vermelho = Cor(255, 0, 0);
let origem = Ponto(0.0, 0.0, 0.0);

// Unit struct (sem campos)
struct Marcador;

Implementação (impl)

struct Retangulo {
    largura: f64,
    altura: f64,
}

impl Retangulo {
    // Função associada (construtor - sem self)
    fn novo(largura: f64, altura: f64) -> Self {
        Retangulo { largura, altura }
    }

    fn quadrado(lado: f64) -> Self {
        Retangulo { largura: lado, altura: lado }
    }

    // Método (recebe &self)
    fn area(&self) -> f64 {
        self.largura * self.altura
    }

    fn perimetro(&self) -> f64 {
        2.0 * (self.largura + self.altura)
    }

    // Método que modifica o struct (&mut self)
    fn redimensionar(&mut self, fator: f64) {
        self.largura *= fator;
        self.altura *= fator;
    }

    // Método que consome o struct (self)
    fn em_tupla(self) -> (f64, f64) {
        (self.largura, self.altura)
    }
}

let mut r = Retangulo::novo(10.0, 5.0);
println!("Área: {}", r.area());        // 50.0
r.redimensionar(2.0);
let tupla = r.em_tupla();              // r é consumido

Enums

// Enum simples
enum Direcao {
    Norte,
    Sul,
    Leste,
    Oeste,
}

// Enum com dados
enum Mensagem {
    Sair,                       // sem dados
    Mover { x: i32, y: i32 },  // campos nomeados
    Escrever(String),           // String
    Cor(u8, u8, u8),            // tupla
}

impl Mensagem {
    fn processar(&self) {
        match self {
            Mensagem::Sair => println!("Saindo..."),
            Mensagem::Mover { x, y } => println!("Mover para ({}, {})", x, y),
            Mensagem::Escrever(texto) => println!("{}", texto),
            Mensagem::Cor(r, g, b) => println!("Cor: ({}, {}, {})", r, g, b),
        }
    }
}

// Enum com derive
#[derive(Debug, Clone, PartialEq)]
enum Status {
    Pendente,
    EmProgresso(f64),   // porcentagem
    Concluido,
    Erro(String),
}

Traits

Definição e Implementação

// Definindo um trait
trait Resumo {
    // Método obrigatório
    fn resumir(&self) -> String;

    // Método com implementação padrão
    fn previa(&self) -> String {
        format!("{}...", &self.resumir()[..20])
    }
}

// Implementando para um struct
struct Artigo {
    titulo: String,
    autor: String,
    conteudo: String,
}

impl Resumo for Artigo {
    fn resumir(&self) -> String {
        format!("{}, por {} - {}", self.titulo, self.autor, &self.conteudo[..50])
    }
    // previa() usa a implementação padrão
}

// Implementando para outro tipo
struct Tweet {
    usuario: String,
    texto: String,
}

impl Resumo for Tweet {
    fn resumir(&self) -> String {
        format!("@{}: {}", self.usuario, self.texto)
    }
}

Trait Bounds (Restrições de Tipo)

// Usando impl Trait (syntax sugar)
fn notificar(item: &impl Resumo) {
    println!("Novidade: {}", item.resumir());
}

// Usando trait bound (equivalente)
fn notificar2<T: Resumo>(item: &T) {
    println!("Novidade: {}", item.resumir());
}

// Múltiplos bounds
fn exibir(item: &(impl Resumo + std::fmt::Display)) {
    println!("{}", item);
}

// Com where (mais legível para muitos bounds)
fn processar<T, U>(t: &T, u: &U) -> String
where
    T: Resumo + Clone,
    U: Resumo + std::fmt::Debug,
{
    format!("{} - {:?}", t.resumir(), u)
}

// Retornando impl Trait
fn criar_resumivel() -> impl Resumo {
    Tweet {
        usuario: String::from("bot"),
        texto: String::from("Olá mundo!"),
    }
}

Trait Objects (Despacho Dinâmico)

// Trait object com Box<dyn Trait>
fn obter_resumivel(tipo: &str) -> Box<dyn Resumo> {
    match tipo {
        "artigo" => Box::new(Artigo {
            titulo: "Titulo".into(),
            autor: "Autor".into(),
            conteudo: "Conteúdo longo aqui que ultrapassa cinquenta caracteres facilmente".into(),
        }),
        _ => Box::new(Tweet {
            usuario: "user".into(),
            texto: "tweet".into(),
        }),
    }
}

// Vetor de trait objects
let itens: Vec<Box<dyn Resumo>> = vec![
    Box::new(Artigo { /* ... */ }),
    Box::new(Tweet { /* ... */ }),
];

for item in &itens {
    println!("{}", item.resumir());
}

Traits Comuns da Biblioteca Padrão

TraitUsoDerivável
DebugFormatação com {:?}Sim
DisplayFormatação com {}Nao
CloneCópia explícita (.clone())Sim
CopyCópia implícita (stack only)Sim
PartialEq / EqComparação == e !=Sim
PartialOrd / OrdComparação <, >, etc.Sim
HashHashing para HashMap/HashSetSim
DefaultValor padrão (Default::default())Sim
From / IntoConversão entre tiposNao
IteratorIteração com next()Nao
DropLimpeza ao sair de escopoNao
Deref / DerefMutDereferência customizadaNao
SendPode ser enviado entre threadsAuto
SyncPode ser compartilhado entre threadsAuto
// Derive automático
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct Config {
    nome: String,
    valor: i32,
}

// Implementação manual de Display
use std::fmt;

impl fmt::Display for Config {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}={}", self.nome, self.valor)
    }
}

// From/Into
impl From<(String, i32)> for Config {
    fn from((nome, valor): (String, i32)) -> Self {
        Config { nome, valor }
    }
}

let c: Config = ("chave".to_string(), 42).into();

Collections

Vec (Vetor Dinâmico)

// Criação
let mut v1: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3, 4, 5];
let v3 = vec![0; 10];           // 10 elementos, todos zero
let v4: Vec<i32> = (0..10).collect();

// Adicionar/remover
v1.push(1);
v1.push(2);
v1.push(3);
let ultimo = v1.pop();           // Some(3)
v1.insert(0, 99);               // inserir na posição 0
v1.remove(0);                   // remover da posição 0

// Acessar
let primeiro = &v2[0];          // panic se fora dos limites
let segundo = v2.get(1);        // retorna Option<&T>

// Iteração
for val in &v2 {
    println!("{}", val);
}
for val in &mut v1 {
    *val *= 2;
}

// Métodos úteis
v1.len();                        // tamanho
v1.is_empty();                   // está vazio?
v1.contains(&2);                 // contém o valor?
v1.sort();                       // ordenar
v1.dedup();                      // remover duplicatas consecutivas
v1.retain(|&x| x > 0);          // manter apenas positivos
v1.extend(&[10, 20, 30]);       // adicionar vários
let fatia: &[i32] = &v1[1..3];  // slice

HashMap

use std::collections::HashMap;

// Criação
let mut mapa: HashMap<String, i32> = HashMap::new();
let mapa2: HashMap<&str, i32> = HashMap::from([
    ("um", 1),
    ("dois", 2),
    ("três", 3),
]);

// Inserir
mapa.insert("pontos".to_string(), 100);

// Acessar
let valor = mapa.get("pontos");              // Option<&i32>
let valor2 = mapa["pontos"];                  // panic se não existe

// Entry API (inserir se não existe)
mapa.entry("pontos".to_string()).or_insert(0);
*mapa.entry("pontos".to_string()).or_insert(0) += 10;

// Contagem de palavras com Entry API
let texto = "olá mundo olá rust olá";
let mut contagem: HashMap<&str, i32> = HashMap::new();
for palavra in texto.split_whitespace() {
    *contagem.entry(palavra).or_insert(0) += 1;
}

// Iteração
for (chave, valor) in &mapa {
    println!("{}: {}", chave, valor);
}

// Métodos úteis
mapa.len();
mapa.is_empty();
mapa.contains_key("pontos");
mapa.remove("pontos");
mapa.keys();                  // iterador sobre chaves
mapa.values();                // iterador sobre valores

HashSet

use std::collections::HashSet;

let mut conjunto: HashSet<i32> = HashSet::new();
conjunto.insert(1);
conjunto.insert(2);
conjunto.insert(3);
conjunto.insert(2);  // duplicata, não é inserida

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

// Operações de conjunto
let uniao: HashSet<_> = a.union(&b).collect();            // {1,2,3,4}
let intersecao: HashSet<_> = a.intersection(&b).collect(); // {2,3}
let diferenca: HashSet<_> = a.difference(&b).collect();    // {1}
let simetrica: HashSet<_> = a.symmetric_difference(&b).collect(); // {1,4}

conjunto.contains(&2);   // true
conjunto.remove(&2);
conjunto.len();

BTreeMap e VecDeque

use std::collections::BTreeMap;
use std::collections::VecDeque;

// BTreeMap - como HashMap, mas com chaves ordenadas
let mut bt: BTreeMap<&str, i32> = BTreeMap::new();
bt.insert("banana", 3);
bt.insert("abacaxi", 1);
bt.insert("cereja", 2);
// Iteração é sempre em ordem alfabética das chaves
for (chave, valor) in &bt {
    println!("{}: {}", chave, valor); // abacaxi, banana, cereja
}
bt.range("abacaxi"..="banana"); // range query

// VecDeque - fila de duas pontas (deque)
let mut deque: VecDeque<i32> = VecDeque::new();
deque.push_back(1);     // adicionar no final
deque.push_back(2);
deque.push_front(0);    // adicionar no início
deque.pop_front();      // remover do início: Some(0)
deque.pop_back();       // remover do final: Some(2)

Error Handling

Option

// Option<T> = Some(T) | None
fn encontrar(lista: &[i32], alvo: i32) -> Option<usize> {
    for (i, &val) in lista.iter().enumerate() {
        if val == alvo {
            return Some(i);
        }
    }
    None
}

let resultado = encontrar(&[10, 20, 30], 20);

// Tratando Option
match resultado {
    Some(indice) => println!("Encontrado no índice {}", indice),
    None => println!("Não encontrado"),
}

// Métodos úteis de Option
let x: Option<i32> = Some(42);
x.unwrap();              // 42 (panic se None)
x.unwrap_or(0);          // 42 (ou 0 se None)
x.unwrap_or_default();   // 42 (ou Default::default() se None)
x.unwrap_or_else(|| calcular_padrao()); // com closure
x.expect("mensagem");    // 42 (panic com mensagem se None)
x.is_some();             // true
x.is_none();             // false
x.map(|v| v * 2);        // Some(84)
x.and_then(|v| if v > 0 { Some(v) } else { None }); // flatmap
x.filter(|&v| v > 0);    // Some(42)
x.or(Some(0));            // Some(42), usa alternativa se None
x.zip(Some("a"));         // Some((42, "a"))

// if let e while let
if let Some(valor) = x {
    println!("{}", valor);
}

Result

// Result<T, E> = Ok(T) | Err(E)
use std::fs;
use std::io;
use std::num::ParseIntError;

fn ler_numero(caminho: &str) -> Result<i32, String> {
    let conteudo = fs::read_to_string(caminho)
        .map_err(|e| format!("Erro ao ler arquivo: {}", e))?;
    let numero: i32 = conteudo.trim().parse()
        .map_err(|e| format!("Erro ao converter: {}", e))?;
    Ok(numero)
}

// Tratando Result
match ler_numero("dados.txt") {
    Ok(n) => println!("Número: {}", n),
    Err(e) => eprintln!("Erro: {}", e),
}

// Métodos úteis de Result
let r: Result<i32, String> = Ok(42);
r.unwrap();              // 42 (panic se Err)
r.unwrap_or(0);          // 42 (ou 0 se Err)
r.expect("falhou");      // 42 (panic com mensagem se Err)
r.is_ok();               // true
r.is_err();              // false
r.ok();                  // Option<T>: Some(42)
r.err();                 // Option<E>: None
r.map(|v| v * 2);        // Ok(84)
r.map_err(|e| format!("Erro: {}", e));
r.and_then(|v| Ok(v + 1)); // flatmap
r.or(Ok(0));              // Ok(42), usa alternativa se Err

Operador ? (Propagação de Erros)

use std::io;
use std::fs;

// O operador ? propaga o erro automaticamente
fn ler_config() -> Result<String, io::Error> {
    let conteudo = fs::read_to_string("config.toml")?;  // propaga Err
    Ok(conteudo)
}

// Encadeando com ?
fn obter_porta() -> Result<u16, Box<dyn std::error::Error>> {
    let config = fs::read_to_string("config.toml")?;
    let porta: u16 = config.trim().parse()?;
    Ok(porta)
}

// Erro customizado
#[derive(Debug)]
enum AppErro {
    Io(io::Error),
    Parse(std::num::ParseIntError),
    Custom(String),
}

impl std::fmt::Display for AppErro {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AppErro::Io(e) => write!(f, "Erro de IO: {}", e),
            AppErro::Parse(e) => write!(f, "Erro de parse: {}", e),
            AppErro::Custom(msg) => write!(f, "{}", msg),
        }
    }
}

impl std::error::Error for AppErro {}

impl From<io::Error> for AppErro {
    fn from(e: io::Error) -> Self { AppErro::Io(e) }
}

impl From<std::num::ParseIntError> for AppErro {
    fn from(e: std::num::ParseIntError) -> Self { AppErro::Parse(e) }
}

Iterators

Criando e Consumindo Iteradores

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

// Três formas de criar iteradores de coleções
v.iter();       // itera sobre &T
v.iter_mut();   // itera sobre &mut T
v.into_iter();  // itera sobre T (consome a coleção)

// Ranges como iteradores
(0..10);         // 0 a 9
(0..=10);        // 0 a 10
('a'..='z');     // 'a' a 'z'

Adaptadores (Lazy - não executam até serem consumidos)

let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// map - transforma cada elemento
let dobros: Vec<i32> = v.iter().map(|x| x * 2).collect();

// filter - mantém apenas os que satisfazem o predicado
let pares: Vec<&i32> = v.iter().filter(|&&x| x % 2 == 0).collect();

// filter_map - filtra e transforma em uma operação
let resultados: Vec<i32> = vec!["1", "abc", "3"]
    .iter()
    .filter_map(|s| s.parse::<i32>().ok())
    .collect(); // [1, 3]

// flat_map - map que achata o resultado
let palavras: Vec<&str> = vec!["olá mundo", "boa noite"]
    .iter()
    .flat_map(|s| s.split_whitespace())
    .collect(); // ["olá", "mundo", "boa", "noite"]

// enumerate - adiciona índice
for (i, val) in v.iter().enumerate() {
    println!("{}: {}", i, val);
}

// zip - combina dois iteradores
let nomes = vec!["Ana", "Bia"];
let idades = vec![30, 25];
let pares: Vec<_> = nomes.iter().zip(idades.iter()).collect();
// [("Ana", 30), ("Bia", 25)]

// chain - concatena dois iteradores
let a = vec![1, 2, 3];
let b = vec![4, 5, 6];
let todos: Vec<_> = a.iter().chain(b.iter()).collect();
// [1, 2, 3, 4, 5, 6]

// take / skip
let primeiros: Vec<_> = v.iter().take(3).collect();  // [1, 2, 3]
let restante: Vec<_> = v.iter().skip(7).collect();   // [8, 9, 10]

// take_while / skip_while
let ate: Vec<_> = v.iter().take_while(|&&x| x < 5).collect();
let depois: Vec<_> = v.iter().skip_while(|&&x| x < 5).collect();

// peekable - permite espiar o próximo sem consumir
let mut iter = v.iter().peekable();
if iter.peek() == Some(&&1) {
    println!("Começa com 1");
}

// chunks (em slices)
for pedaco in v.chunks(3) {
    println!("{:?}", pedaco); // [1,2,3], [4,5,6], [7,8,9], [10]
}

// windows (em slices)
for janela in v.windows(3) {
    println!("{:?}", janela); // [1,2,3], [2,3,4], [3,4,5], ...
}

// step_by
let de_dois: Vec<_> = (0..10).step_by(2).collect(); // [0, 2, 4, 6, 8]

Consumidores

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

// collect - transforma iterador em coleção
let conjunto: HashSet<_> = v.iter().collect();
let mapa: HashMap<_, _> = vec![("a", 1), ("b", 2)].into_iter().collect();

// fold - acumula um resultado
let soma = v.iter().fold(0, |acc, &x| acc + x);       // 15
let produto = v.iter().fold(1, |acc, &x| acc * x);     // 120

// reduce - como fold, mas usa o primeiro elemento como acumulador
let soma = v.iter().copied().reduce(|a, b| a + b);     // Some(15)

// sum e product
let soma: i32 = v.iter().sum();        // 15
let produto: i32 = v.iter().product(); // 120

// count, min, max
let n = v.iter().count();              // 5
let min = v.iter().min();              // Some(&1)
let max = v.iter().max();              // Some(&5)

// any, all
let tem_par = v.iter().any(|&x| x % 2 == 0);   // true
let todos_pos = v.iter().all(|&x| x > 0);       // true

// find, position
let primeiro_par = v.iter().find(|&&x| x % 2 == 0);     // Some(&2)
let pos_par = v.iter().position(|&x| x % 2 == 0);       // Some(1)

// for_each
v.iter().for_each(|x| println!("{}", x));

// partition - divide em dois
let (pares, impares): (Vec<_>, Vec<_>) = v.iter().partition(|&&x| x % 2 == 0);

// unzip
let pares = vec![(1, "a"), (2, "b"), (3, "c")];
let (numeros, letras): (Vec<i32>, Vec<&str>) = pares.into_iter().unzip();

Implementando Iterator

struct Contador {
    valor: u32,
    maximo: u32,
}

impl Contador {
    fn novo(maximo: u32) -> Self {
        Contador { valor: 0, maximo }
    }
}

impl Iterator for Contador {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.valor < self.maximo {
            self.valor += 1;
            Some(self.valor)
        } else {
            None
        }
    }
}

let soma: u32 = Contador::novo(5).sum(); // 15

String Operations

Criação e Conversão

let s1 = String::new();                    // string vazia
let s2 = String::from("olá");             // de &str
let s3 = "olá".to_string();               // de &str
let s4 = String::with_capacity(100);       // com capacidade pré-alocada
let s5 = format!("{}-{}-{}", 2024, 1, 15); // formatação
let s6 = ['o', 'l', 'á'].iter().collect::<String>(); // de chars

Manipulação

let mut s = String::from("Olá");

// Adicionar conteúdo
s.push(' ');                    // adicionar char
s.push_str("Mundo");           // adicionar &str
s += "!";                      // concatenar (Add trait)

// Inserir e remover
s.insert(0, '¡');              // inserir char na posição
s.insert_str(0, "-> ");        // inserir &str na posição
s.remove(0);                   // remover char na posição (por byte index)
s.truncate(3);                 // manter apenas os primeiros 3 bytes
s.clear();                     // limpar toda a string

// Concatenação
let a = String::from("Olá");
let b = String::from(" Mundo");
let c = a + &b;               // a é movido, b é emprestado
let d = format!("{}{}", c, "!"); // sem mover ninguém

// Repetição
let rep = "ha".repeat(3);     // "hahaha"

Busca e Fatiamento

let s = String::from("Olá, Mundo! Bem-vindo ao Rust.");

// Verificações
s.contains("Mundo");           // true
s.starts_with("Olá");         // true
s.ends_with("Rust.");          // true
s.is_empty();                  // false
s.len();                       // tamanho em bytes
s.chars().count();             // tamanho em caracteres

// Busca
s.find("Mundo");               // Some(5) - posição em bytes
s.rfind("o");                  // posição da última ocorrência

// Split
let partes: Vec<&str> = s.split(", ").collect();
let palavras: Vec<&str> = s.split_whitespace().collect();
let linhas: Vec<&str> = "a\nb\nc".lines().collect();
let (esq, dir) = s.split_once(", ").unwrap(); // divide em 2

// Trim
let t = "  olá  ";
t.trim();                      // "olá"
t.trim_start();                // "olá  "
t.trim_end();                  // "  olá"

// Substituição
s.replace("Mundo", "Brasil");  // nova String
s.replacen("o", "0", 2);      // substitui apenas 2 ocorrências

// Maiúsculas/minúsculas
"olá".to_uppercase();          // "OLÁ"
"OLÁ".to_lowercase();         // "olá"

// Slice (cuidado: por byte index, não char index)
let fatia = &s[0..3];          // "Olá" - CUIDADO com caracteres multibyte

Formatação

// println! e format!
println!("Inteiro: {}", 42);
println!("Float: {:.2}", 3.14159);       // 2 casas decimais
println!("Padding: {:>10}", "dir");       // alinhado à direita
println!("Padding: {:<10}", "esq");       // alinhado à esquerda
println!("Padding: {:^10}", "centro");    // centralizado
println!("Padding: {:0>5}", 42);          // "00042"
println!("Debug: {:?}", vec![1, 2, 3]);   // formatação Debug
println!("Pretty: {:#?}", vec![1, 2, 3]); // Debug formatado
println!("Binário: {:b}", 42);            // "101010"
println!("Hex: {:x}", 255);               // "ff"
println!("Hex: {:X}", 255);               // "FF"
println!("Octal: {:o}", 8);               // "10"
println!("Nomeado: {nome}", nome = "Rust");

Smart Pointers

Box<T> - Alocação no Heap

// Box: ponteiro para dado no heap
let b = Box::new(5);
println!("{}", b);  // dereferência automática

// Uso principal: tipos de tamanho desconhecido em tempo de compilação
enum Lista {
    Cons(i32, Box<Lista>),  // tipo recursivo precisa de Box
    Nil,
}

let lista = Lista::Cons(1,
    Box::new(Lista::Cons(2,
        Box::new(Lista::Cons(3,
            Box::new(Lista::Nil))))));

// Trait objects
let animal: Box<dyn std::fmt::Debug> = Box::new("gato");

Rc<T> - Reference Counting (Single-thread)

use std::rc::Rc;

// Rc: múltiplos owners do mesmo dado (imutável, single-thread)
let a = Rc::new(String::from("dados compartilhados"));
let b = Rc::clone(&a);  // incrementa o contador
let c = a.clone();       // equivalente

println!("Contagem: {}", Rc::strong_count(&a)); // 3

// Quando b e c saem de escopo, o contador diminui
// Quando chega a 0, o dado é liberado

Arc<T> - Atomic Reference Counting (Multi-thread)

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

// Arc: como Rc, mas thread-safe (usa contagem atômica)
let dados = Arc::new(vec![1, 2, 3]);

let mut handles = vec![];
for _ in 0..3 {
    let dados_clone = Arc::clone(&dados);
    let handle = thread::spawn(move || {
        println!("{:?}", dados_clone);
    });
    handles.push(handle);
}

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

RefCell<T> - Mutabilidade Interior

use std::cell::RefCell;

// RefCell: permite mutação mesmo com referência imutável
// Verifica regras de borrowing em RUNTIME (não em compilação)
let dados = RefCell::new(vec![1, 2, 3]);

// Empréstimo imutável
let r = dados.borrow();
println!("{:?}", r);
drop(r);  // liberar antes de pegar empréstimo mutável

// Empréstimo mutável
dados.borrow_mut().push(4);

// Padrão comum: Rc<RefCell<T>> - múltiplos owners com mutação
use std::rc::Rc;

let compartilhado = Rc::new(RefCell::new(0));
let clone1 = Rc::clone(&compartilhado);
let clone2 = Rc::clone(&compartilhado);

*clone1.borrow_mut() += 10;
*clone2.borrow_mut() += 20;
println!("{}", compartilhado.borrow()); // 30

Mutex<T> - Exclusão Mútua (Multi-thread)

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

let contador = Arc::new(Mutex::new(0));

let mut handles = vec![];
for _ in 0..10 {
    let contador_clone = Arc::clone(&contador);
    let handle = thread::spawn(move || {
        let mut num = contador_clone.lock().unwrap();
        *num += 1;
    });
    handles.push(handle);
}

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

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

Resumo de Smart Pointers

TipoOwnershipThread-safeMutaçãoUso
Box<T>ÚnicoSim*Sim (se mut)Alocação no heap
Rc<T>CompartilhadoNaoNaoVários owners (single-thread)
Arc<T>CompartilhadoSimNaoVários owners (multi-thread)
RefCell<T>ÚnicoNaoSim (runtime)Mutabilidade interior
Mutex<T>CompartilhadoSimSim (com lock)Acesso exclusivo entre threads
Cell<T>ÚnicoNaoSim (Copy types)Mutabilidade interior (Copy)
RwLock<T>CompartilhadoSimSim (com lock)Vários leitores OU um escritor

Async/Await

Fundamentos

// Função assíncrona
async fn buscar_dados(url: &str) -> Result<String, reqwest::Error> {
    let resposta = reqwest::get(url).await?;
    let texto = resposta.text().await?;
    Ok(texto)
}

// Bloco async
let futuro = async {
    let resultado = buscar_dados("https://api.exemplo.com").await;
    println!("{:?}", resultado);
};

// Executando com tokio
#[tokio::main]
async fn main() {
    let dados = buscar_dados("https://api.exemplo.com").await.unwrap();
    println!("{}", dados);
}

Concorrência com Tokio

use tokio;

#[tokio::main]
async fn main() {
    // Spawn: executa tarefa em background
    let handle = tokio::spawn(async {
        // trabalho assíncrono
        "resultado"
    });
    let resultado = handle.await.unwrap();

    // join! - executa múltiplas futures concorrentemente
    let (r1, r2, r3) = tokio::join!(
        buscar_dados("url1"),
        buscar_dados("url2"),
        buscar_dados("url3"),
    );

    // select! - espera a primeira future completar
    tokio::select! {
        val = buscar_dados("url1") => println!("url1: {:?}", val),
        val = buscar_dados("url2") => println!("url2: {:?}", val),
    }

    // Timeout
    use tokio::time::{timeout, Duration};
    match timeout(Duration::from_secs(5), buscar_dados("url")).await {
        Ok(resultado) => println!("{:?}", resultado),
        Err(_) => println!("Timeout!"),
    }

    // Sleep
    tokio::time::sleep(Duration::from_millis(100)).await;
}

Streams (Iteradores Assíncronos)

use tokio_stream::StreamExt;  // crate tokio-stream

async fn processar_stream() {
    let stream = tokio_stream::iter(vec![1, 2, 3, 4, 5]);

    // Consumir stream
    let mut stream = stream.map(|x| x * 2).filter(|x| *x > 4);

    while let Some(valor) = stream.next().await {
        println!("{}", valor);
    }
}

Channels Assíncronos

use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    // Canal com buffer
    let (tx, mut rx) = mpsc::channel::<String>(32);

    let tx2 = tx.clone();

    tokio::spawn(async move {
        tx.send("olá".to_string()).await.unwrap();
    });

    tokio::spawn(async move {
        tx2.send("mundo".to_string()).await.unwrap();
    });

    while let Some(msg) = rx.recv().await {
        println!("Recebido: {}", msg);
    }
}

Macros Úteis

MacroDescriçãoExemplo
println!Imprime com nova linhaprintln!("x = {}", x);
eprintln!Imprime no stderreprintln!("Erro: {}", e);
print!Imprime sem nova linhaprint!("Carregando...");
format!Formata e retorna Stringlet s = format!("{}-{}", a, b);
dbg!Debug: imprime expressão e valordbg!(x * 2 + 1);
vec!Cria Veclet v = vec![1, 2, 3];
todo!Placeholder (panic com “not yet implemented”)todo!("implementar depois");
unimplemented!Marca código não implementadounimplemented!();
unreachable!Marca código inalcançávelunreachable!("nunca deveria chegar aqui");
panic!Encerra com mensagem de erropanic!("erro fatal");
assert!Verificação (panic se falso)assert!(x > 0);
assert_eq!Igualdade (panic se diferente)assert_eq!(soma, 42);
assert_ne!Desigualdade (panic se igual)assert_ne!(a, b);
debug_assert!Assert apenas em modo debugdebug_assert!(caro_check());
cfg!Verifica config de compilaçãoif cfg!(target_os = "linux") {}
include_str!Inclui arquivo como &strlet sql = include_str!("query.sql");
include_bytes!Inclui arquivo como &[u8]let img = include_bytes!("logo.png");
env!Variável de ambiente (compilação)let ver = env!("CARGO_PKG_VERSION");
concat!Concatena literaislet s = concat!("a", "b", "c");
stringify!Converte tokens em &strlet s = stringify!(1 + 2);
write! / writeln!Escreve em bufferwrite!(buf, "x={}", x)?;
// dbg! - imprime arquivo, linha, expressão e valor
let x = 5;
let y = dbg!(x * 2) + 1;  // [src/main.rs:2] x * 2 = 10
// y = 11

// vec! com repetição
let zeros = vec![0; 100];  // 100 zeros

// assert com mensagem customizada
assert!(x > 0, "x deveria ser positivo, mas era {}", x);
assert_eq!(resultado, esperado, "Resultado inesperado para input {}", input);

Cargo Commands

Comandos Essenciais

ComandoDescrição
cargo new meu_projetoCriar novo projeto (binário)
cargo new meu_lib --libCriar nova biblioteca
cargo initInicializar projeto no diretório atual
cargo buildCompilar (modo debug)
cargo build --releaseCompilar (modo release, otimizado)
cargo runCompilar e executar
cargo run -- arg1 arg2Executar com argumentos
cargo testRodar todos os testes
cargo test nome_testeRodar testes que contêm o nome
cargo test -- --nocaptureTestes com stdout visível
cargo benchRodar benchmarks
cargo doc --openGerar e abrir documentação
cargo checkVerificar sem compilar (mais rápido)
cargo cleanLimpar artefatos de compilação
cargo updateAtualizar dependências
cargo publishPublicar no crates.io
cargo clippyLinter (sugestões de melhorias)
cargo fmtFormatar código
cargo add nome_crateAdicionar dependência
cargo remove nome_crateRemover dependência
cargo treeÁrvore de dependências
cargo auditVerificar vulnerabilidades
cargo watch -x runRecompilar ao salvar (precisa instalar)

Cargo.toml

[package]
name = "meu_projeto"
version = "0.1.0"
edition = "2021"
authors = ["Seu Nome <email@exemplo.com>"]
description = "Descrição do projeto"
license = "MIT"

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

[dev-dependencies]
criterion = "0.5"

[build-dependencies]
cc = "1.0"

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

[features]
default = ["feature_a"]
feature_a = []
feature_b = ["dep:alguma_crate"]

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

[[example]]
name = "exemplo1"
path = "examples/exemplo1.rs"

Estrutura de Projeto

meu_projeto/
├── Cargo.toml
├── Cargo.lock
├── src/
│   ├── main.rs          # ponto de entrada (binário)
│   ├── lib.rs           # ponto de entrada (biblioteca)
│   ├── modulo.rs        # módulo
│   └── modulo/
│       ├── mod.rs       # módulo com submódulos
│       └── sub.rs       # submódulo
├── tests/
│   └── integracao.rs    # testes de integração
├── benches/
│   └── benchmark.rs     # benchmarks
├── examples/
│   └── exemplo.rs       # exemplos (cargo run --example exemplo)
└── build.rs             # build script

Atributos Comuns

Derive

#[derive(Debug)]              // habilita {:?}
#[derive(Clone)]              // habilita .clone()
#[derive(Copy, Clone)]        // cópia implícita (requer Clone)
#[derive(PartialEq, Eq)]     // comparação == e !=
#[derive(PartialOrd, Ord)]   // comparação <, >, <=, >=
#[derive(Hash)]               // hashing
#[derive(Default)]            // Default::default()
#[derive(serde::Serialize, serde::Deserialize)] // serialização (crate serde)

Testes

#[test]                       // marca função como teste
fn teste_soma() {
    assert_eq!(2 + 2, 4);
}

#[test]
#[should_panic]               // teste deve causar panic
fn teste_divisao_zero() {
    dividir(1, 0);
}

#[test]
#[should_panic(expected = "divisão por zero")]
fn teste_com_mensagem() {
    dividir(1, 0);
}

#[test]
#[ignore]                     // ignorar por padrão (cargo test -- --ignored)
fn teste_lento() {
    // teste demorado
}

#[cfg(test)]                  // módulo compilado apenas em modo teste
mod tests {
    use super::*;

    #[test]
    fn teste_interno() {
        // pode acessar itens privados do módulo pai
    }
}

Compilação Condicional

#[cfg(target_os = "linux")]
fn somente_linux() {}

#[cfg(target_os = "windows")]
fn somente_windows() {}

#[cfg(feature = "avancado")]
fn recurso_avancado() {}

#[cfg(debug_assertions)]
fn somente_debug() {}

#[cfg(test)]
mod testes { /* ... */ }

// Negação
#[cfg(not(target_os = "windows"))]
fn nao_windows() {}

// Combinação
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn linux_64() {}

#[cfg(any(target_os = "linux", target_os = "macos"))]
fn unix_like() {}

Outros Atributos Úteis

#[allow(dead_code)]            // silenciar aviso de código não usado
#[allow(unused_variables)]     // silenciar aviso de variável não usada
#[allow(unused_imports)]       // silenciar aviso de import não usado
#[warn(missing_docs)]          // avisar sobre documentação faltando
#[deny(unsafe_code)]           // proibir código unsafe

#[inline]                      // sugerir inlining ao compilador
#[inline(always)]              // forçar inlining
#[inline(never)]               // nunca fazer inline

#[must_use]                    // aviso se o retorno for ignorado
fn calcular() -> i32 { 42 }

#[deprecated(since = "2.0", note = "Use nova_funcao() em vez disso")]
fn funcao_antiga() {}

#[doc = "Documentação da função"]
/// Documentação (equivalente ao #[doc])
fn documentada() {}

#[repr(C)]                     // layout de memória compatível com C
struct DadosC {
    x: i32,
    y: i32,
}

#[repr(u8)]                    // enum com representação u8
enum Bandeira {
    A = 0,
    B = 1,
    C = 2,
}

#[non_exhaustive]              // permite adicionar variantes no futuro
enum Versao {
    V1,
    V2,
}

Módulos

Declaração e Uso

// Definindo módulo inline
mod matematica {
    pub fn somar(a: i32, b: i32) -> i32 { a + b }

    fn auxiliar() {}  // privado por padrão

    pub mod avancado {
        pub fn potencia(base: i32, exp: u32) -> i32 {
            (0..exp).fold(1, |acc, _| acc * base)
        }
    }
}

// Usando
use matematica::somar;
use matematica::avancado::potencia;

// use com alias
use std::collections::HashMap as Mapa;

// use com glob (evitar em produção)
use std::collections::*;

// Re-exportar
pub use matematica::somar;

// Paths
crate::matematica::somar(1, 2);  // caminho absoluto desde a raiz do crate
self::somar(1, 2);               // caminho relativo ao módulo atual
super::funcao();                 // caminho para o módulo pai

Testes

Testes Unitários e de Integração

// src/lib.rs
pub fn somar(a: i32, b: i32) -> i32 { a + b }

pub fn dividir(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("divisão por zero".to_string())
    } else {
        Ok(a / b)
    }
}

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

    #[test]
    fn teste_somar() {
        assert_eq!(somar(2, 3), 5);
    }

    #[test]
    fn teste_somar_negativos() {
        assert_eq!(somar(-1, -2), -3);
    }

    #[test]
    fn teste_dividir_ok() {
        let resultado = dividir(10.0, 2.0);
        assert!(resultado.is_ok());
        assert_eq!(resultado.unwrap(), 5.0);
    }

    #[test]
    fn teste_dividir_por_zero() {
        let resultado = dividir(10.0, 0.0);
        assert!(resultado.is_err());
    }

    // Teste que retorna Result
    #[test]
    fn teste_com_result() -> Result<(), String> {
        let r = dividir(10.0, 2.0)?;
        assert_eq!(r, 5.0);
        Ok(())
    }
}
// tests/integracao.rs (teste de integração)
use meu_projeto::somar;

#[test]
fn teste_integracao_somar() {
    assert_eq!(somar(100, 200), 300);
}

Dicas Rápidas

Conversões Comuns

// Número para String
let s = 42.to_string();
let s = format!("{}", 42);

// String para número
let n: i32 = "42".parse().unwrap();
let n = "42".parse::<f64>().unwrap();

// Vec<u8> para String
let s = String::from_utf8(vec![72, 101, 108, 108, 111]).unwrap();

// String para Vec<u8>
let bytes = "Hello".as_bytes().to_vec();
let bytes: Vec<u8> = "Hello".bytes().collect();

// &str para &[u8]
let bytes: &[u8] = "hello".as_bytes();

// Slice para Vec
let v = [1, 2, 3].to_vec();
let v: Vec<i32> = [1, 2, 3].into();

// Vec para slice
let s: &[i32] = &v;

// Option para Result
let r: Result<i32, &str> = Some(42).ok_or("vazio");

// Result para Option
let o: Option<i32> = Ok(42).ok();

Padrões Idiomáticos

// Builder pattern
struct Config {
    host: String,
    porta: u16,
    timeout: u64,
}

impl Config {
    fn novo() -> Self {
        Config {
            host: "localhost".into(),
            porta: 8080,
            timeout: 30,
        }
    }

    fn host(mut self, host: &str) -> Self {
        self.host = host.into();
        self
    }

    fn porta(mut self, porta: u16) -> Self {
        self.porta = porta;
        self
    }

    fn timeout(mut self, timeout: u64) -> Self {
        self.timeout = timeout;
        self
    }
}

let config = Config::novo()
    .host("0.0.0.0")
    .porta(3000)
    .timeout(60);

// Newtype pattern
struct Email(String);
struct Idade(u8);

// Type alias
type Resultado<T> = Result<T, Box<dyn std::error::Error>>;