---
title: "Tratamento de Erros em Rust: Result e Option | Rust Brasil"
url: "https://rustlang.com.br/tutoriais/tratamento-de-erros/"
markdown_url: "https://rustlang.com.br/tutoriais/tratamento-de-erros.MD"
description: "Domine Result, Option e o operador ? em Rust. Tutorial completo com thiserror, anyhow e erros personalizados em português."
date: "2026-02-21"
author: "Equipe Rust Brasil"
---

# Tratamento de Erros em Rust: Result e Option | Rust Brasil

Domine Result, Option e o operador ? em Rust. Tutorial completo com thiserror, anyhow e erros personalizados em português.


Rust adota uma abordagem única para tratamento de erros: em vez de exceções (como Java/Python) ou códigos de retorno (como C), Rust usa **tipos algébricos** — `Result<T, E>` e `Option<T>` — que forçam o programador a lidar com erros explicitamente. Isso resulta em código mais robusto e confiável.

## Dois Tipos de Erros em Rust

Rust distingue entre dois tipos de erros:

1. **Erros recuperáveis** — Situações esperadas que o programa pode tratar (arquivo não encontrado, input inválido). Representados por `Result<T, E>`.
2. **Erros irrecuperáveis** — Bugs que indicam um estado inválido do programa (acesso fora dos limites de um array). Causam `panic!`.

### panic! — Erros Irrecuperáveis

Quando algo dá muito errado e não há como continuar:

```rust
fn main() {
    // panic! explícito
    // panic!("Algo deu muito errado!");

    // panic! implícito (acesso fora dos limites)
    let vetor = vec![1, 2, 3];
    // let valor = vetor[99]; // panic: index out of bounds

    println!("Este código roda normalmente.");
    println!("Use panic! apenas para bugs, não para erros esperados.");
}
```

Na prática, você raramente usa `panic!` diretamente. Use `Result` para erros que podem acontecer normalmente.

## Option<T> — Valores que Podem Não Existir

`Option<T>` representa um valor que pode ou não estar presente. É a alternativa segura ao `null` de outras linguagens:

```rust
// Definição na biblioteca padrão:
// enum Option<T> {
//     Some(T),   // contém um valor
//     None,      // não contém valor
// }

fn encontrar_primeiro_par(numeros: &[i32]) -> Option<i32> {
    for &n in numeros {
        if n % 2 == 0 {
            return Some(n);
        }
    }
    None
}

fn dividir_seguro(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

fn main() {
    // Usando match
    let numeros = vec![1, 3, 5, 8, 9];
    match encontrar_primeiro_par(&numeros) {
        Some(n) => println!("Primeiro par: {}", n),
        None => println!("Nenhum número par encontrado"),
    }

    // Usando if let
    if let Some(resultado) = dividir_seguro(10.0, 3.0) {
        println!("10 / 3 = {:.2}", resultado);
    }

    if let Some(resultado) = dividir_seguro(10.0, 0.0) {
        println!("10 / 0 = {:.2}", resultado);
    } else {
        println!("Divisão por zero!");
    }
}
```

### Métodos Úteis de Option

```rust
fn main() {
    let algum_valor: Option<i32> = Some(42);
    let nenhum_valor: Option<i32> = None;

    // unwrap_or: valor padrão se None
    println!("{}", algum_valor.unwrap_or(0));   // 42
    println!("{}", nenhum_valor.unwrap_or(0));  // 0

    // unwrap_or_else: closure para calcular valor padrão
    let resultado = nenhum_valor.unwrap_or_else(|| {
        println!("Calculando valor padrão...");
        -1
    });
    println!("Resultado: {}", resultado);

    // map: transforma o valor interno
    let texto: Option<String> = Some(String::from("olá"));
    let maiusculo: Option<String> = texto.map(|s| s.to_uppercase());
    println!("{:?}", maiusculo); // Some("OLÁ")

    // and_then (flatmap): encadeia operações que retornam Option
    let numero: Option<&str> = Some("42");
    let parsed: Option<i32> = numero.and_then(|s| s.parse().ok());
    println!("{:?}", parsed); // Some(42)

    // is_some e is_none
    println!("algum_valor existe? {}", algum_valor.is_some()); // true
    println!("nenhum_valor existe? {}", nenhum_valor.is_some()); // false

    // filter: mantém Some apenas se a condição for verdadeira
    let valor = Some(10);
    let par = valor.filter(|&x| x % 2 == 0);
    let impar = valor.filter(|&x| x % 2 != 0);
    println!("Par: {:?}, Ímpar: {:?}", par, impar); // Some(10), None
}
```

## Result<T, E> — Operações que Podem Falhar

`Result<T, E>` representa o resultado de uma operação que pode suceder (`Ok(T)`) ou falhar (`Err(E)`):

```rust
// Definição na biblioteca padrão:
// enum Result<T, E> {
//     Ok(T),    // sucesso com valor T
//     Err(E),   // erro com valor E
// }

use std::num::ParseIntError;

fn parse_idade(texto: &str) -> Result<u32, String> {
    match texto.trim().parse::<u32>() {
        Ok(idade) if idade <= 150 => Ok(idade),
        Ok(idade) => Err(format!("Idade {} é inválida (máximo 150)", idade)),
        Err(e) => Err(format!("Não foi possível converter '{}': {}", texto, e)),
    }
}

fn main() {
    let entradas = vec!["25", "abc", "200", " 30 ", "-5"];

    for entrada in entradas {
        match parse_idade(entrada) {
            Ok(idade) => println!("'{}' -> Idade válida: {} anos", entrada, idade),
            Err(erro) => println!("'{}' -> Erro: {}", entrada, erro),
        }
    }
}
```

Saída:

```
'25' -> Idade válida: 25 anos
'abc' -> Erro: Não foi possível converter 'abc': invalid digit found in string
'200' -> Erro: Idade 200 é inválida (máximo 150)
' 30 ' -> Idade válida: 30 anos
'-5' -> Erro: Não foi possível converter '-5': invalid digit found in string
```

## unwrap e expect

Para prototipagem rápida, `unwrap` e `expect` extraem o valor de um `Result` ou `Option`, mas causam panic se houver erro:

```rust
fn main() {
    // unwrap: panic com mensagem genérica se for Err/None
    let numero: i32 = "42".parse().unwrap();
    println!("Número: {}", numero);

    // expect: panic com SUA mensagem se for Err/None
    let numero: i32 = "42".parse().expect("Falha ao converter número");
    println!("Número: {}", numero);

    // PERIGOSO — causaria panic:
    // let _erro: i32 = "abc".parse().unwrap();
    // let _erro: i32 = "abc".parse().expect("Não é um número válido");

    // unwrap é aceitável quando você TEM CERTEZA que não vai falhar
    let lista = vec![1, 2, 3];
    let primeiro = lista.first().unwrap(); // sabemos que a lista não está vazia
    println!("Primeiro: {}", primeiro);
}
```

**Regra geral:** Use `unwrap`/`expect` apenas em protótipos, testes, ou quando você tem certeza absoluta de que o valor existe. Em código de produção, trate os erros adequadamente.

## O Operador ? — Propagação Elegante de Erros

O operador `?` é o recurso mais elegante do Rust para tratamento de erros. Ele propaga o erro automaticamente para quem chamou a função:

```rust
use std::fs;
use std::io;

fn ler_nome_do_arquivo(caminho: &str) -> Result<String, io::Error> {
    let conteudo = fs::read_to_string(caminho)?; // se Err, retorna o erro
    Ok(conteudo.trim().to_string())
}

// Sem o operador ?, seria assim:
fn ler_nome_do_arquivo_verbose(caminho: &str) -> Result<String, io::Error> {
    let conteudo = match fs::read_to_string(caminho) {
        Ok(c) => c,
        Err(e) => return Err(e),
    };
    Ok(conteudo.trim().to_string())
}

fn main() {
    match ler_nome_do_arquivo("/tmp/teste.txt") {
        Ok(conteudo) => println!("Conteúdo: {}", conteudo),
        Err(e) => println!("Erro ao ler arquivo: {}", e),
    }
}
```

### Encadeando o Operador ?

O verdadeiro poder do `?` aparece quando você encadeia várias operações que podem falhar:

```rust
use std::fs;
use std::io;
use std::path::Path;

fn contar_linhas_arquivo(caminho: &str) -> Result<usize, io::Error> {
    let conteudo = fs::read_to_string(caminho)?;
    let linhas = conteudo.lines().count();
    Ok(linhas)
}

fn processar_arquivo(caminho: &str) -> Result<String, io::Error> {
    let conteudo = fs::read_to_string(caminho)?;
    let linhas: Vec<&str> = conteudo.lines().collect();
    let total = linhas.len();

    let resumo = format!(
        "Arquivo: {}\nLinhas: {}\nPrimeira linha: {}",
        caminho,
        total,
        linhas.first().unwrap_or(&"(vazio)")
    );

    Ok(resumo)
}

fn main() {
    // Exemplo com arquivo que pode não existir
    match processar_arquivo("dados.txt") {
        Ok(resumo) => println!("{}", resumo),
        Err(e) => println!("Não foi possível processar: {}", e),
    }
}
```

### O Operador ? com Option

O `?` também funciona com `Option`, retornando `None` se o valor não existir:

```rust
fn obter_extensao(nome_arquivo: &str) -> Option<&str> {
    let ponto_pos = nome_arquivo.rfind('.')?;  // retorna None se não encontrar
    Some(&nome_arquivo[ponto_pos + 1..])
}

fn obter_nome_sem_extensao(nome_arquivo: &str) -> Option<&str> {
    let ponto_pos = nome_arquivo.rfind('.')?;
    Some(&nome_arquivo[..ponto_pos])
}

fn main() {
    let arquivos = vec!["foto.jpg", "documento.pdf", "README", "codigo.rs"];

    for arquivo in arquivos {
        match obter_extensao(arquivo) {
            Some(ext) => println!("{} -> extensão: {}", arquivo, ext),
            None => println!("{} -> sem extensão", arquivo),
        }
    }
}
```

## Tipos de Erro Personalizados

Para aplicações reais, você vai querer criar seus próprios tipos de erro:

```rust
use std::fmt;
use std::num::ParseIntError;

#[derive(Debug)]
enum AppErro {
    ArquivoNaoEncontrado(String),
    FormatoInvalido(String),
    ParseErro(ParseIntError),
    SemPermissao,
}

// Implementar Display para mensagens amigáveis
impl fmt::Display for AppErro {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppErro::ArquivoNaoEncontrado(caminho) => {
                write!(f, "Arquivo não encontrado: {}", caminho)
            }
            AppErro::FormatoInvalido(msg) => {
                write!(f, "Formato inválido: {}", msg)
            }
            AppErro::ParseErro(e) => {
                write!(f, "Erro ao converter valor: {}", e)
            }
            AppErro::SemPermissao => {
                write!(f, "Sem permissão para executar esta operação")
            }
        }
    }
}

// Converter ParseIntError em AppErro automaticamente
impl From<ParseIntError> for AppErro {
    fn from(e: ParseIntError) -> Self {
        AppErro::ParseErro(e)
    }
}

fn processar_linha(linha: &str) -> Result<i32, AppErro> {
    if linha.is_empty() {
        return Err(AppErro::FormatoInvalido(
            "Linha vazia".to_string()
        ));
    }

    // O ? converte ParseIntError em AppErro automaticamente (via From)
    let numero: i32 = linha.trim().parse()?;
    Ok(numero * 2)
}

fn main() {
    let linhas = vec!["42", "", "abc", "7"];

    for linha in linhas {
        match processar_linha(linha) {
            Ok(resultado) => println!("'{}' -> {}", linha, resultado),
            Err(e) => println!("'{}' -> Erro: {}", linha, e),
        }
    }
}
```

Saída:

```
'42' -> 84
'' -> Erro: Formato inválido: Linha vazia
'abc' -> Erro: Erro ao converter valor: invalid digit found in string
'7' -> 14
```

## thiserror: Erros Personalizados Simplificados

A crate `thiserror` elimina o boilerplate de criar tipos de erro. Adicione ao `Cargo.toml`:

```toml
[dependencies]
thiserror = "2"
```

Agora compare a versão manual com a versão usando `thiserror`:

```rust
use thiserror::Error;

#[derive(Debug, Error)]
enum AppErro {
    #[error("Arquivo não encontrado: {0}")]
    ArquivoNaoEncontrado(String),

    #[error("Formato inválido: {0}")]
    FormatoInvalido(String),

    #[error("Erro ao converter valor")]
    ParseErro(#[from] std::num::ParseIntError),

    #[error("Sem permissão para executar esta operação")]
    SemPermissao,

    #[error("Erro de IO: {0}")]
    IoErro(#[from] std::io::Error),
}

fn processar_arquivo(caminho: &str) -> Result<Vec<i32>, AppErro> {
    let conteudo = std::fs::read_to_string(caminho)
        .map_err(|_| AppErro::ArquivoNaoEncontrado(caminho.to_string()))?;

    let mut numeros = Vec::new();
    for linha in conteudo.lines() {
        if !linha.is_empty() {
            let n: i32 = linha.trim().parse()?;  // converte automaticamente
            numeros.push(n);
        }
    }

    Ok(numeros)
}

fn main() {
    match processar_arquivo("numeros.txt") {
        Ok(nums) => println!("Números: {:?}", nums),
        Err(e) => println!("Erro: {}", e),
    }
}
```

O `thiserror` gera automaticamente as implementações de `Display`, `Error` e `From`. Muito menos código para o mesmo resultado!

## anyhow: Tratamento de Erros Simplificado para Aplicações

Enquanto `thiserror` é ideal para **bibliotecas** (onde você quer tipos de erro bem definidos), a crate `anyhow` é perfeita para **aplicações** (onde você quer simplicidade).

Adicione ao `Cargo.toml`:

```toml
[dependencies]
anyhow = "1"
```

```rust
use anyhow::{Context, Result, bail, ensure};

fn ler_configuracao(caminho: &str) -> Result<String> {
    let conteudo = std::fs::read_to_string(caminho)
        .context(format!("Falha ao ler arquivo de configuração: {}", caminho))?;

    ensure!(!conteudo.is_empty(), "Arquivo de configuração está vazio");

    Ok(conteudo)
}

fn parse_porta(texto: &str) -> Result<u16> {
    let porta: u16 = texto.parse()
        .context(format!("'{}' não é uma porta válida", texto))?;

    if porta < 1024 {
        bail!("Porta {} requer privilégios de administrador", porta);
    }

    Ok(porta)
}

fn iniciar_app() -> Result<()> {
    let _config = ler_configuracao("config.toml")
        .context("Falha ao inicializar aplicação")?;

    let _porta = parse_porta("8080")
        .context("Falha ao configurar porta do servidor")?;

    println!("Aplicação iniciada com sucesso!");
    Ok(())
}

fn main() {
    if let Err(e) = iniciar_app() {
        // anyhow mostra a cadeia completa de erros
        eprintln!("Erro: {}", e);

        // Mostrar a cadeia de contextos
        for causa in e.chain().skip(1) {
            eprintln!("  Causado por: {}", causa);
        }
    }
}
```

Funcionalidades do `anyhow`:
- **`Result<T>`** — Alias para `Result<T, anyhow::Error>`, aceita qualquer tipo de erro
- **`.context()`** — Adiciona contexto a um erro (muito útil para debugging)
- **`bail!`** — Retorna um erro imediatamente (como um `return Err(...)` mais conciso)
- **`ensure!`** — Verifica uma condição e retorna erro se falsa

## thiserror vs. anyhow: Quando Usar Cada Um

| Cenário | Use | Por quê |
|---------|-----|---------|
| Biblioteca (crate pública) | `thiserror` | Usuários precisam de tipos de erro bem definidos para tratá-los |
| Aplicação (binário) | `anyhow` | Simplicidade; erros geralmente são reportados ao usuário |
| Código interno complexo | Ambos | `thiserror` para domínio, `anyhow` na camada de aplicação |

## Padrões Comuns no Dia a Dia

### Convertendo entre Option e Result

```rust
fn main() {
    // Option -> Result
    let valor: Option<i32> = Some(42);
    let resultado: Result<i32, &str> = valor.ok_or("Valor não encontrado");
    println!("{:?}", resultado); // Ok(42)

    let nada: Option<i32> = None;
    let resultado: Result<i32, &str> = nada.ok_or("Valor não encontrado");
    println!("{:?}", resultado); // Err("Valor não encontrado")

    // Result -> Option
    let ok: Result<i32, &str> = Ok(42);
    let opcao: Option<i32> = ok.ok();
    println!("{:?}", opcao); // Some(42)
}
```

### Coletando Results de um Iterator

```rust
fn main() {
    let textos = vec!["1", "2", "3", "4", "5"];

    // Coletar todos ou falhar no primeiro erro
    let numeros: Result<Vec<i32>, _> = textos
        .iter()
        .map(|t| t.parse::<i32>())
        .collect();

    println!("Todos válidos: {:?}", numeros); // Ok([1, 2, 3, 4, 5])

    let textos_mistos = vec!["1", "dois", "3"];
    let resultado: Result<Vec<i32>, _> = textos_mistos
        .iter()
        .map(|t| t.parse::<i32>())
        .collect();

    println!("Com erro: {:?}", resultado); // Err(ParseIntError)

    // Separar sucessos de erros
    let (sucessos, erros): (Vec<_>, Vec<_>) = textos_mistos
        .iter()
        .map(|t| t.parse::<i32>())
        .partition(Result::is_ok);

    let sucessos: Vec<i32> = sucessos.into_iter().map(Result::unwrap).collect();
    let erros: Vec<_> = erros.into_iter().map(Result::unwrap_err).collect();

    println!("Sucessos: {:?}", sucessos);  // [1, 3]
    println!("Erros: {:?}", erros);        // [invalid digit found in string]
}
```

## Exemplo Prático: Validador de Dados

```rust
use std::collections::HashMap;

#[derive(Debug)]
struct ValidacaoErro {
    campo: String,
    mensagem: String,
}

impl std::fmt::Display for ValidacaoErro {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Campo '{}': {}", self.campo, self.mensagem)
    }
}

fn validar_nome(nome: &str) -> Result<(), ValidacaoErro> {
    if nome.trim().is_empty() {
        return Err(ValidacaoErro {
            campo: "nome".to_string(),
            mensagem: "não pode estar vazio".to_string(),
        });
    }
    if nome.len() < 3 {
        return Err(ValidacaoErro {
            campo: "nome".to_string(),
            mensagem: "deve ter pelo menos 3 caracteres".to_string(),
        });
    }
    Ok(())
}

fn validar_email(email: &str) -> Result<(), ValidacaoErro> {
    if !email.contains('@') {
        return Err(ValidacaoErro {
            campo: "email".to_string(),
            mensagem: "deve conter @".to_string(),
        });
    }
    if !email.contains('.') {
        return Err(ValidacaoErro {
            campo: "email".to_string(),
            mensagem: "deve conter um domínio válido".to_string(),
        });
    }
    Ok(())
}

fn validar_idade(idade: &str) -> Result<u32, ValidacaoErro> {
    let idade: u32 = idade.parse().map_err(|_| ValidacaoErro {
        campo: "idade".to_string(),
        mensagem: format!("'{}' não é um número válido", idade),
    })?;

    if idade < 18 {
        return Err(ValidacaoErro {
            campo: "idade".to_string(),
            mensagem: "deve ser maior de 18 anos".to_string(),
        });
    }

    Ok(idade)
}

fn validar_cadastro(dados: &HashMap<&str, &str>) -> Result<(), Vec<ValidacaoErro>> {
    let mut erros = Vec::new();

    if let Some(nome) = dados.get("nome") {
        if let Err(e) = validar_nome(nome) {
            erros.push(e);
        }
    } else {
        erros.push(ValidacaoErro {
            campo: "nome".to_string(),
            mensagem: "campo obrigatório".to_string(),
        });
    }

    if let Some(email) = dados.get("email") {
        if let Err(e) = validar_email(email) {
            erros.push(e);
        }
    } else {
        erros.push(ValidacaoErro {
            campo: "email".to_string(),
            mensagem: "campo obrigatório".to_string(),
        });
    }

    if let Some(idade) = dados.get("idade") {
        if let Err(e) = validar_idade(idade) {
            erros.push(e);
        }
    }

    if erros.is_empty() {
        Ok(())
    } else {
        Err(erros)
    }
}

fn main() {
    println!("=== Cadastro Válido ===");
    let mut dados = HashMap::new();
    dados.insert("nome", "Maria Silva");
    dados.insert("email", "maria@exemplo.com");
    dados.insert("idade", "25");

    match validar_cadastro(&dados) {
        Ok(()) => println!("Cadastro válido!"),
        Err(erros) => {
            println!("Erros encontrados:");
            for e in &erros {
                println!("  - {}", e);
            }
        }
    }

    println!("\n=== Cadastro Inválido ===");
    let mut dados_invalidos = HashMap::new();
    dados_invalidos.insert("nome", "Al");
    dados_invalidos.insert("email", "invalido");
    dados_invalidos.insert("idade", "15");

    match validar_cadastro(&dados_invalidos) {
        Ok(()) => println!("Cadastro válido!"),
        Err(erros) => {
            println!("Erros encontrados:");
            for e in &erros {
                println!("  - {}", e);
            }
        }
    }
}
```

Saída:

```
=== Cadastro Válido ===
Cadastro válido!

=== Cadastro Inválido ===
Erros encontrados:
  - Campo 'nome': deve ter pelo menos 3 caracteres
  - Campo 'email': deve conter @
  - Campo 'idade': deve ser maior de 18 anos
```

## Boas Práticas para Tratamento de Erros

1. **Nunca use `unwrap()` em código de produção** — a menos que tenha 100% de certeza de que o valor existe
2. **Prefira `?` em vez de `match` para propagação** — é mais legível
3. **Use `context()` do anyhow** — facilita muito o debugging em produção
4. **Crie tipos de erro específicos** para bibliotecas com `thiserror`
5. **Use `anyhow` para aplicações** — menos boilerplate, mais produtividade
6. **Erros devem ser informativos** — inclua contexto sobre o que aconteceu e por quê
7. **Documente quais erros uma função pode retornar** — ajuda quem usa seu código

## Veja Também

- [Boas Práticas de Error Handling em Rust](/artigos/boas-praticas-error-handling/) — padrões avançados para projetos de produção
- [anyhow e thiserror: Quando Usar Cada Um](/ecossistema/anyhow-thiserror/) — guia detalhado das crates mais populares para erros

## Conclusão

O sistema de tratamento de erros do Rust pode parecer verboso no início, mas ele te força a pensar sobre o que pode dar errado — e isso resulta em software muito mais robusto. Com `Result`, `Option`, o operador `?` e crates como `thiserror` e `anyhow`, você tem todas as ferramentas para escrever código que lida com erros de forma elegante e segura.

Parabéns por completar esta série de tutoriais introdutórios! Agora você tem uma base sólida em Rust. Continue praticando, explore o [Rust Book](https://doc.rust-lang.org/book/) e participe da [comunidade Rust Brasil](/) para trocar experiências com outros Rustáceos!

---

Cada linguagem tem sua filosofia para tratamento de erros — compare abordagens:

- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Tratamento de erros em Go</a> — o padrão if err != nil e error wrapping com fmt.Errorf
- <a href="https://kotlin.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'kotlin.dev.br' })">Exceptions em Kotlin</a> — try/catch, Result e tratamento funcional de erros
