Rust: Tratamento de erros no Rust: `Result` e o operador `?`

Última atualização: 2026-08-26

O tratamento de erros é uma das características de design mais notáveis do Rust — ele não depende de exceções, mas, em vez disso, usa o sistema de tipos para codificar a “falha potencial” no tipo de retorno, e o compilador garante que você nunca deixe passar nenhum erro.

O Rust não possui try-catch nem throw. Em vez disso, ele utiliza a enumeração Result<T, E> e o operador ? — que separam o tratamento de erros da lógica de negócios normal, resultando em um código que é ao mesmo tempo seguro e conciso.


1. Uma história sobre como lidar com problemas, desde o pedido de comida para viagem até a retirada do pedido

(1) O processo de entrega de comida na vida real

Xiao Ming está fazendo hora extra hoje e decidiu pedir comida para viagem. Há vários momentos no processo em que as coisas podem dar errado:

Etapa Processo normal Erros possíveis
1. Encontrar um restaurante Abrir o aplicativo de entrega de comida e procurar um restaurante Não consigo encontrar um restaurante
2. Carregar menu Navegar pelos itens do menu Falha ao carregar o menu (tempo limite de rede)
3. Enviar pagamento Pagar Falha no pagamento (saldo insuficiente)
4. Aguardando preparação Aguarde 30 minutos A loja cancela o pedido
5. Retirar Receber o pedido para viagem Descobrir que o pedido foi cancelado no momento da retirada

Cada etapa pode ser bem-sucedida (Ok) ou falhar (Err). Vamos modelar isso usando a abordagem do Rust:

RUST
// ============================================
// Use Result to simulate every step of ordering takeout
// ============================================

// Defining Possible Errors
#[derive(Debug)]
enum OrderError {
    RestaurantNotFound,
    MenuLoadFailed,
    PaymentFailed(String),
    OrderCancelled,
}

// Simulate Store Search
fn find_restaurant(name: &str) -> Result<String, OrderError> {
    let available = vec!["PizzaHouse", "SushiBar", "NoodleShop"];
    if available.contains(&name) {
        Ok(format!("Found: {}", name))
    } else {
        Err(OrderError::RestaurantNotFound)
    }
}

// Simulate the Load Menu
fn load_menu(restaurant: &str) -> Result<Vec<&str>, OrderError> {
    if restaurant.contains("Pizza") {
        Ok(vec!["Margherita", "Pepperoni", "Hawaiian"])
    } else {
        Err(OrderError::MenuLoadFailed)
    }
}

// Simulated Payment
fn process_payment(amount: f64) -> Result<String, OrderError> {
    if amount < 100.0 {
        Ok(format!("Paid: ${:.2}", amount))
    } else {
        Err(OrderError::PaymentFailed("Insufficient balance".into()))
    }
}

fn main() {
    // Go through the entire process from start to finish
    let restaurant = find_restaurant("PizzaHouse");
    match restaurant {
        Ok(msg) => println!("Step 1: {}", msg),
        Err(e) => println!("Step 1 failed: {:?}", e),
    }
}

Em cada etapa, um valor de retorno de Result<T, E>Ok(T) indica sucesso, enquanto Err(E) indica falha. O chamador deve lidar explicitamente com ambas as possibilidades; não é possível “esquecer de tratar os erros”.


2. Diagramas conceituais

O fluxograma Mermaid a seguir ilustra os quatro caminhos principais para o tratamento de erros Result<T, E>: processamento detalhado via match, propagação de operadores via ?, avaliação rápida via unwrap/expect e uma situação de pânico:

100%
graph TB
    A["Result&lt;T, E&gt;"] --> B["Ok(T)<br/>Success"]
    A --> C["Err(E)<br/>Failure"]

    B --> D["Continue execution<br/>Standard Procedure"]

    C --> E["match Processing<br/>Fine-Grained Branch Control"]
    C --> F["? Operators<br/>Propagation Error"]
    C --> G["unwrap / expect<br/>Quick Value Retrieval"]

    E --> H["For different errors<br/>Handle them separately"]
    F --> I["Caller Function<br/>Receive Err"]
    G --> J["panic!<br/>Program Crash"]

    H --> K["Restore Default Values<br/>Or retry logic"]
    I --> L["match in main<br/>Centralized Processing"]

    style A fill:#e1f5fe,stroke:#0288d1
    style B fill:#c8e6c9,stroke:#388e3c
    style C fill:#ffcdd2,stroke:#d32f2f
    style J fill:#ffcdd2,stroke:#d32f2f

3. O que você vai aprender


4. Conceitos fundamentais

100%
graph TB
    A[Error Handling Strategies] --> B[Recoverable errors<br>Recoverable]
    A --> C[Unrecoverable error<br>Unrecoverable]

    B --> D["Result<T, E>"]
    D --> E["Ok(T) Success Score"]
    D --> F["Err(E) Error value"]

    F --> G["match Precision Processing"]
    F --> H["? Top-down communication"]
    F --> I["unwrap/expect Quick Value Retrieval<br>(There are risks)"]

    C --> J["panic!"]
    J --> K["The program crashed and exited"]
    J --> L["Applicable:Bug/Irreversible state"]

    B -.-> M["Custom Error Types"]
    M --> N["Implementation Display + Debug"]
    M --> O["From trait Convert"]

(1) Comparação entre quatro estratégias de tratamento de erros

Estratégia Caso de uso Vantagens Desvantagens
panic! Erros irrecuperáveis (como acesso fora dos limites de um array ou falhas de asserção) Falha rápida para identificar o problema O programa trava imediatamente
unwrap / expect Desenvolvimento de protótipos / Garantia de que não falhará Código conciso Entra em pânico imediatamente quando ocorre um erro — não é muito elegante
match / if let Exige um tratamento diferente para cada tipo de erro Controle detalhado sobre a lógica de tratamento de erros Código prolixo
? Operador Propaga erros entre funções; tratados de maneira uniforme no nível superior Mais conciso; mantém a lógica principal clara Deve ser usado em funções que retornam um Result

(2) Result<T, E> Guia de Referência Rápida

Método Assinatura Finalidade Comportamento em caso de falha
unwrap() Result<T,E> -> T Extrair o valor de Ok panic!
expect(msg) Result<T,E> -> T Recuperar o valor de “Ok” e personalizar a mensagem de emergência panic!(msg)
unwrap_or(default) Result<T,E> -> T Retorna um valor em caso de sucesso; retorna um valor padrão em caso de falha Retorna um valor padrão
unwrap_or_else(fn) Result<T,E> -> T Retorna um valor em caso de sucesso; executa o closure em caso de falha Executa o closure
is_ok() Result<T,E> -> bool Verificar se foi bem-sucedido
is_err() Result<T,E> -> bool Verificar se há falha
ok() Result<T,E> -> Option<T> Converter para Option None
err() Result<T,E> -> Option<E> Para Option None
map(fn) Result<T,E> -> Result<U,E> Valor de sucesso da conversão Inalterado
map_err(fn) Result<T,E> -> Result<T,F> Converter valores incorretos Converter tipos de erro
and_then(fn) Result<T,E> -> Result<U,E> Operações subsequentes em chamadas encadeadas Avaliação por curto-circuito

5. Exemplos

▶ Exemplo 1: Tratamento básico de “Result” e “match” (Dificuldade ⭐)

RUST
// ============================================
// Use Result to handle division-by-zero errors
// ============================================

fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    // Use match to handle both success and failure scenarios
    println!("=== match Processing ===");
    match safe_divide(10.0, 2.0) {
        Ok(result) => println!("10 / 2 = {}", result),
        Err(msg) => println!("Error: {}", msg),
    }

    match safe_divide(10.0, 0.0) {
        Ok(result) => println!("10 / 0 = {}", result),
        Err(msg) => println!("Error: {}", msg),
    }

    // Use unwrap_or to provide a default value
    println!("\n=== unwrap_or Default value ===");
    let result1 = safe_divide(10.0, 2.0).unwrap_or(0.0);
    let result2 = safe_divide(10.0, 0.0).unwrap_or(0.0);
    println!("10 / 2 = {}", result1);
    println!("10 / 0 = {} (default)", result2);

    // Use unwrap_or_else to execute a closure
    println!("\n=== unwrap_or_else Closure ===");
    let result3 = safe_divide(10.0, 2.0).unwrap_or_else(|e| {
        eprintln!("Warning: {}, using default", e);
        0.0
    });
    println!("Result: {}", result3);

    // ⚠️ unwrap will panic (Uncomment the code below to try it out)
    // let crash = safe_divide(10.0, 0.0).unwrap();
    // println!("Will not reach here");
}

Resultado:

TEXT 📖 Somente leitura
=== match Processing ===
10 / 2 = 5
Error: Division by zero

=== unwrap_or Default value ===
10 / 2 = 5
10 / 0 = 0 (default)

=== unwrap_or_else Closure ===
Result: 5

match Oferece o tratamento mais abrangente — você pode escrever lógicas de tratamento separadas para Ok e Err. unwrap_or e unwrap_or_else são atalhos: eles fornecem valores padrão em caso de falha. unwrap() é a opção mais arriscada — ela pressupõe que o sucesso é garantido e trava imediatamente se houver falha.


▶ Exemplo 2: Operador ? — Erro de propagação em cadeia (Dificuldade ⭐⭐)

RUST
// ============================================
// ? Operators: Error propagation with Result
// Only when returning Result can only be used within the function ?
// ============================================

use std::fs::File;
use std::io::{self, Read};

// Read the entire contents of the file
// ? indicates:If File::open Failure,Return Now Err
//         If read_to_string Failure,Return Now Err
fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;          // Open the file,If it fails, return
    let mut content = String::new();
    file.read_to_string(&mut content)?;         // Read the content,If it fails, return
    Ok(content)
}

// Do not use ? An equivalent way of writing——The amount of code has doubled
fn read_file_without_question(path: &str) -> Result<String, io::Error> {
    let mut file = match File::open(path) {
        Ok(f) => f,
        Err(e) => return Err(e),
    };
    let mut content = String::new();
    match file.read_to_string(&mut content) {
        Ok(_) => Ok(content),
        Err(e) => Err(e),
    }
}

// Chain Call ?——More concise
fn read_file_chain(path: &str) -> Result<String, io::Error> {
    let mut content = String::new();
    File::open(path)?.read_to_string(&mut content)?;
    Ok(content)
}

fn main() {
    // Test files that can and cannot be read, respectively
    let files = vec!["Cargo.toml", "nonexistent.txt"];

    for file in &files {
        match read_file(file) {
            Ok(content) => {
                println!("--- {} ({} bytes) ---", file, content.len());
                println!("{}", &content[..content.len().min(80)]);
            }
            Err(e) => {
                println!("Failed to read '{}': {}", file, e);
            }
        }
    }
}

Saída (supondo que Cargo.toml exista e não esteja vazio):

TEXT 📖 Somente leitura
--- Cargo.toml (42 bytes) ---
[package]
name = "demo"
version = "0.1.0"
edition = "2021"

Failed to read 'nonexistent.txt': The system cannot find the file specified. (os error 2)

O operador ? é a essência do tratamento de erros em Rust: ele funciona como uma forma abreviada de “retornar antecipadamente em caso de falha”. expr? é equivalente a match expr { Ok(v) => v, Err(e) => return Err(e.into()) }. Observe que ? chama automaticamente From::from para realizar a conversão de tipos de erro — isso é fundamental para sua capacidade de se propagar entre diferentes tipos de erro.


▶ Exemplo 3: Tipos de erro personalizados — Implementação de exibição + depuração (Dificuldade ⭐⭐)

RUST
// ============================================
// Custom Error Types:Make Your Error Messages More Informative
// To be implemented std::fmt::Display + std::fmt::Debug
// ============================================

use std::fmt;
use std::num::ParseIntError;

// Custom Error Enumeration
#[derive(Debug)]
enum AppError {
    /// Input is empty
    EmptyInput,
    /// Failed to parse the number,Includes the original string
    ParseFailed(String),
    /// The value is outside the allowed range
    OutOfRange { value: i32, min: i32, max: i32 },
    /// Division by Zero
    DivisionByZero,
}

// Implementation Display——Control the error messages users see
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::EmptyInput => {
                write!(f, "Input cannot be empty")
            }
            AppError::ParseFailed(input) => {
                write!(f, "Failed to parse '{}' as a number", input)
            }
            AppError::OutOfRange { value, min, max } => {
                write!(f, "Value {} is out of range [{}, {}]", value, min, max)
            }
            AppError::DivisionByZero => {
                write!(f, "Division by zero is not allowed")
            }
        }
    }
}

// Implement From<ParseIntError> — let ? operator convert automatically
impl From<ParseIntError> for AppError {
    fn from(_: ParseIntError) -> Self {
        AppError::ParseFailed("unknown".into())
    }
}

// Processing User Input:Analyze and Verify
fn process_input(input: &str, divisor: i32) -> Result<i32, AppError> {
    if input.is_empty() {
        return Err(AppError::EmptyInput);
    }
    let value: i32 = input.parse().map_err(|_| {
        // Manually Convert Error Types
        AppError::ParseFailed(input.to_string())
    })?;
    if value < -100 || value > 100 {
        return Err(AppError::OutOfRange {
            value,
            min: -100,
            max: 100,
        });
    }
    if divisor == 0 {
        return Err(AppError::DivisionByZero);
    }
    Ok(value / divisor)
}

fn main() {
    let test_cases = vec![
        ("42", 2,    "Normal case"),
        ("",   1,    "Empty input"),
        ("abc", 1,   "Parse error"),
        ("999", 1,   "Out of range"),
        ("50",  0,   "Division by zero"),
    ];

    for (input, divisor, description) in test_cases {
        match process_input(input, divisor) {
            Ok(result) => println!("[{}] OK: {}", description, result),
            Err(e) => println!("[{}] Error: {}", description, e),
        }
    }
}

Resultado:

TEXT 📖 Somente leitura
[Normal case] OK: 21
[Empty input] Error: Input cannot be empty
[Parse error] Error: Failed to parse 'abc' as a number
[Out of range] Error: Value 999 is out of range [-100, 100]
[Division by zero] Error: Division by zero is not allowed

Para definir um tipo de erro personalizado, é necessário implementar Display (a mensagem de erro exibida ao usuário) e Debug (saída de depuração para {:?}). A implementação da característica From<T> permite que o operador ? converta automaticamente um tipo de erro específico para o seu tipo personalizado — esse é o mecanismo central que permite que ? se propague entre os tipos.


▶ Exemplo 4: panic! vs. Estratégias para lidar com valores de retorno de erro (Dificuldade ⭐⭐⭐)

RUST
// ============================================
// Demo panic! Scenarios Where Error Return Values Are Appropriate
// panic! → Unrecoverable error(Bug/Assertion Failed)
// Result → Recoverable errors(User Input/IOFailure)
// ============================================

// --- Suitable for panic! the scene ---

/// Read the port number from the configuration file
/// If the configuration file is missing,This is part of the program Bug,panic That makes sense.
fn get_default_port() -> u16 {
    // This value is hard-coded in the code.,It's impossible for the resolution to fail.
    "8080"
        .parse()
        .expect("Hardcoded port number is invalid")
}

/// A function that accepts only positive integers
/// Passing a negative number indicates that the caller has Bug,panic Quickly Identify Problems
fn sqrt_unchecked(x: i32) -> f64 {
    if x < 0 {
        panic!("sqrt_unchecked called with negative value: {}", x);
    }
    (x as f64).sqrt()
}

// --- Suitable for Result the scene ---

/// Parsing Numbers from User Input
/// It is normal for users to enter the wrong format.,Should be returned Result
fn parse_user_input(input: &str) -> Result<i32, String> {
    input
        .parse()
        .map_err(|_| format!("'{}' is not a valid integer", input))
}

/// Calculate Body Mass Index(BMI)
/// Weight is 0 A negative number may indicate a data error.,Not a program Bug
fn calculate_bmi(weight_kg: f64, height_m: f64) -> Result<f64, String> {
    if weight_kg <= 0.0 {
        return Err("Weight must be positive".to_string());
    }
    if height_m <= 0.0 {
        return Err("Height must be positive".to_string());
    }
    Ok(weight_kg / (height_m * height_m))
}

fn main() {
    // Scene 1:panic Used for unrecoverable errors
    println!("Default port: {}", get_default_port());

    // Scene 2:panic Used for assertions——Invalid parameter
    let value = 16;
    println!("sqrt({}) = {}", value, sqrt_unchecked(value));

    // Scene 3:Result Used for recoverable errors——User Input
    let inputs = vec!["42", "hello", "-5"];
    for input in inputs {
        match parse_user_input(input) {
            Ok(n) => println!("Parsed: {}", n),
            Err(e) => println!("Parse failed: {}", e),
        }
    }

    // Scene 4:Result Used for business logic validation
    let bmi_cases = vec![
        (70.0, 1.75),
        (0.0, 1.70),
        (65.0, -0.5),
    ];
    for (weight, height) in bmi_cases {
        match calculate_bmi(weight, height) {
            Ok(bmi) => println!("BMI: {:.1}", bmi),
            Err(e) => println!("BMI error: {}", e),
        }
    }

    // Scene 5: expect's panic message helps with debugging
    let numbers = vec![10, 20, 30];
    let first = numbers.first().expect("Vector should not be empty");
    println!("First element: {}", first);
}

Resultado:

TEXT 📖 Somente leitura
Default port: 8080
sqrt(16) = 4
Parsed: 42
Parse failed: 'hello' is not a valid integer
Parsed: -5
BMI: 22.9
BMI error: Weight must be positive
BMI error: Height must be positive
First element: 10

Princípios fundamentais para a escolha de uma estratégia: panic! é usado para “bugs no próprio programa” (dados codificados de forma inválida, parâmetros que violam convenções, índice de array fora dos limites); Result é usado para “exceções causadas pelo ambiente externo ou pela entrada do usuário” (erros de E/S, falhas de análise, falhas de validação de negócios). Em resumo: Use Result para problemas que você pode corrigir e use panic para aqueles que você não pode!


6. Uma introdução aos conceitos de anyhow e thiserror

Em projetos em Rust destinados à produção, duas crates da comunidade são amplamente utilizadas para simplificar o tratamento de erros:

Crate Uso principal Casos de uso Principais recursos
de qualquer forma Propagação de erros (perspectiva do chamador) Função principal do aplicativo, ferramentas de CLI, scripts anyhow::Result<T>, .context() Fornecer contexto para o erro
thiserror Definição do erro (da perspectiva do autor da biblioteca) Tipos de erro na API pública da biblioteca Implementado usando a macro derive para gerar automaticamente Display + Error
RUST
// anyhow Style(Concept Examples,No execution required)
// use anyhow::{Context, Result};
//
// fn read_config() -> Result<String> {
//     let content = std::fs::read_to_string("config.toml")
//         .context("Failed to read config file")?;
//     Ok(content)
// }

// thiserror Style(Concept Examples,No execution required)
// use thiserror::Error;
//
// #[derive(Error, Debug)]
// enum MyError {
//     #[error("IO error: {0}")]
//     Io(#[from] std::io::Error),
//
//     #[error("Parse error: {0}")]
//     Parse(#[from] std::num::ParseIntError),
// }

anyhow Incentiva você a se concentrar em “como lidar com erros” em vez de “como definir erros”; thiserror Incentiva você a usar anotações em vez de implementar manualmente Display + From. Normalmente, os dois são usados em conjunto: as bibliotecas utilizam thiserror para definir tipos de erros detalhados, enquanto os aplicativos utilizam anyhow para propagá-los de maneira uniforme.


▶ Exemplo 5: Exercício abrangente — Propagação de erros e recuperação em múltiplas camadas (Dificuldade ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:Custom Errors + ? Dissemination + Recovery Strategy
// ============================================

use std::fmt;

#[derive(Debug)]
enum AppError {
    ParseError(String),
    ValidationError(String),
    NotFound(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::ParseError(msg) => write!(f, "Parsing error: {}", msg),
            AppError::ValidationError(msg) => write!(f, "Validation Error: {}", msg),
            AppError::NotFound(msg) => write!(f, "Not found: {}", msg),
        }
    }
}

impl From<std::num::ParseIntError> for AppError {
    fn from(e: std::num::ParseIntError) -> Self {
        AppError::ParseError(e.to_string())
    }
}

fn parse_age(input: &str) -> Result<u8, AppError> {
    let age: u8 = input.parse().map_err(|_| AppError::ParseError(format!("'{}' Not a valid number", input)))?;
    if age > 150 {
        return Err(AppError::ValidationError(format!("Age {} Unreasonable", age)));
    }
    Ok(age)
}

fn find_user(id: u32) -> Result<String, AppError> {
    let users = [(1, "Alice"), (2, "Bob"), (3, "Charlie")];
    users.iter()
        .find(|(uid, _)| *uid == id)
        .map(|(_, name)| name.to_string())
        .ok_or_else(|| AppError::NotFound(format!("User ID={}", id)))
}

fn process_user(id_str: &str, age_str: &str) -> Result<String, AppError> {
    let id: u32 = id_str.parse().map_err(|_| AppError::ParseError(format!("Invalid ID: '{}'", id_str)))?;
    let age = parse_age(age_str)?;
    let name = find_user(id)?;
    Ok(format!("User: {}, Age: {}", name, age))
}

fn main() {
    let test_cases = [
        ("1", "30"),
        ("2", "200"),
        ("5", "25"),
        ("abc", "30"),
        ("3", "abc"),
    ];

    println!("=== User Processing Test ===");
    for (id, age) in &test_cases {
        match process_user(id, age) {
            Ok(result) => println!("Success: {}", result),
            Err(e) => println!("Failure: {}", e),
        }
    }

    println!("\n=== Batch Processing (Fault-Tolerant) ===");
    let inputs = [("1", "30"), ("5", "25"), ("2", "abc"), ("3", "20")];
    let mut success_count = 0;
    for (id, age) in &inputs {
        match process_user(id, age) {
            Ok(result) => { println!("OK: {}", result); success_count += 1; }
            Err(e) => println!("Skip: {}", e),
        }
    }
    println!("Success: {}/{}", success_count, inputs.len());
}

Resultado:

TEXT 📖 Somente leitura
=== User Processing Test ===
Success: User: Alice, Age: 30
Failure: Validation Error: Age 200 Unreasonable
Failure: Not found: User ID=5
Failure: Parsing error: Invalid ID: 'abc'
Failure: Parsing error: 'abc' Not a valid number

=== Batch Processing (Fault-Tolerant) ===
OK: User: Alice, Age: 30
Skip: Not found: User ID=5
Skip: Parsing error: 'abc' Not a valid number
OK: User: Charlie, Age: 20
Success: 2/4

A enumeração personalizada AppError + a conversão From + a propagação ? tornam a cadeia de tratamento de erros clara e concisa. map_err converte erros subjacentes em tipos personalizados. Para processamento em lote, use match para tolerância a falhas — ele não interrompe o loop, mas registra as falhas e continua.


❓ Perguntas Frequentes

P: unwrap() é considerado uma prática inadequada? R: Sim, a menos que você tenha certeza de que nenhum erro ocorrerá. unwrap() aciona diretamente um panic quando ocorre um erro, sem fornecer informações úteis sobre o erro. Ele pode ser usado em protótipos ou em código de teste, mas em código de produção, priorize o uso de expect("meaningful message") ou ?. Em cenários em que a falha é absolutamente impossível (como na resolução de constantes codificadas), você também pode usar expect para expressar sua certeza.

P: ? operador vs. match tratamento de erros — qual deve ter prioridade? R: Priorize ? para a propagação de erros e use match apenas quando for necessário um tratamento mais detalhado. Na maioria dos casos, uma função não deve tratar os erros que encontra — ela deve repassar o erro ao chamador, permitindo que o código de nível superior decida como tratá-lo. Esse é o princípio da “propagação de erros”. Use match ou unwrap_or_else apenas quando a função atual puder tratar o erro adequadamente.

P: Minha função contém vários tipos de erros (erros de E/S, erros de análise e erros de negócio). Como o ? pode propagá-los de maneira uniforme? R: Por meio da conversão automática realizada pela característica From. Se uma função retornar Result<T, MyError> e ? encontrar io::Error, desde que From<io::Error> for MyError esteja implementado, ? chamará automaticamente From::from para realizar a conversão. É também por isso que o crate thiserror pode gerar automaticamente essas implementações simplesmente usando a anotação #[from].

P: É possível interceptar panic!? R: Sim, mas isso não deve ser usado como um método padrão de tratamento de erros. O Rust oferece std::panic::catch_unwind() para interceptar panics, mas isso se destina principalmente a cenários de FFI (em que o código C não pode tolerar o desenrolamento de panic). Não use catch_unwind como substituto para o tratamento de erros no Rust — isso vai contra a filosofia de design do Rust.

P: Por que os tipos de erro personalizados precisam implementar tanto Display quanto Debug? R: Porque o trait Error do Rust exige ambos. Display controla as mensagens de erro exibidas aos usuários ({}), enquanto Debug controla as informações de depuração ({:?}). A trait Error da biblioteca padrão exige que um tipo implemente impl std::error::Error for MyError {} somente se ele implementar Display + Debug. Em projetos reais, você pode usar thiserror para derivar automaticamente essas implementações.


📖 Resumo


📝 Exercícios

  1. Dificuldade ⭐: Escreva uma função fn parse_age(input: &str) -> Result<u8, String> que analise uma string para obter uma idade (0–150). Se a análise falhar ou o valor estiver fora do intervalo, retorne a mensagem de erro correspondente. Em main, use match para lidar com três cenários: uma idade válida, uma entrada não numérica e um valor fora do intervalo.

  2. Dificuldade ⭐⭐: Escreva um cenário com chamadas de função aninhadas — a função A chama a função B, a função B chama a função C, e cada nível pode falhar. Use o operador ? para propagar erros. Cenário: read_user_file() -> parse_user_data() -> validate_age(). Cada nível retorna o mesmo tipo de erro personalizado UserDataError (definido por meio de uma enumeração), que inclui três variantes: FileNotFound, ParseFailed(String) e InvalidAge(i32).

  3. Dificuldade ⭐⭐⭐: Projete uma minicalculadora que suporte quatro operações: add, subtract, multiply e divide. Todas as operações propagam erros até ?. Requisitos:

    • Definir a enumeração CalcError (DivideByZero, Overflow, InvalidOperator(String))
    • Implementar Display + Debug
    • Use ? para encadear várias operações: expressões de cadeia de caracteres como calculate("10 + 5 * 2")
    • Dica: Primeiro, divida a sequência de caracteres por espaços e, em seguida, processe cada parte individualmente usando fold ou um loop.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%