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:
// ============================================
// 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, enquantoErr(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:
graph TB
A["Result<T, E>"] --> 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
Result<T, E>Enumeração: O tipo principal para o tratamento de erros em Rust, com duas variantes:Ok(T)eErr(E)unwrap/expect: Métodos para obter valores rapidamente e seus riscos?Operador: Uma sintaxe concisa para propagar erros entre funçõesmatchTratamento de erros: Tratamento detalhado de diferentes erros- Tipos de erro personalizados: Implemente
Display+Debugpara tornar as mensagens de erro mais claras panic!vs. Estratégias de seleção de valores de retorno de erro: Qual abordagem usar em quais cenários
4. Conceitos fundamentais
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 ⭐)
// ============================================
// 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:
=== 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
matchOferece o tratamento mais abrangente — você pode escrever lógicas de tratamento separadas paraOkeErr.unwrap_oreunwrap_or_elsesã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 ⭐⭐)
// ============================================
// ? 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):
--- 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 amatch expr { Ok(v) => v, Err(e) => return Err(e.into()) }. Observe que?chama automaticamenteFrom::frompara 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 ⭐⭐)
// ============================================
// 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:
[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) eDebug(saída de depuração para{:?}). A implementação da característicaFrom<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 ⭐⭐⭐)
// ============================================
// 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:
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: UseResultpara problemas que você pode corrigir e usepanicpara 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 |
// 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),
// }
anyhowIncentiva você a se concentrar em “como lidar com erros” em vez de “como definir erros”;thiserrorIncentiva você a usar anotações em vez de implementar manualmenteDisplay+From. Normalmente, os dois são usados em conjunto: as bibliotecas utilizamthiserrorpara definir tipos de erros detalhados, enquanto os aplicativos utilizamanyhowpara propagá-los de maneira uniforme.
▶ Exemplo 5: Exercício abrangente — Propagação de erros e recuperação em múltiplas camadas (Dificuldade ⭐⭐⭐)
// ============================================
// 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:
=== 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ãoFrom+ a propagação?tornam a cadeia de tratamento de erros clara e concisa.map_errconverte erros subjacentes em tipos personalizados. Para processamento em lote, usematchpara 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 deexpect("meaningful message")ou?. Em cenários em que a falha é absolutamente impossível (como na resolução de constantes codificadas), você também pode usarexpectpara expressar sua certeza.
P:
?operador vs.matchtratamento de erros — qual deve ter prioridade? R: Priorize?para a propagação de erros e usematchapenas 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”. Usematchouunwrap_or_elseapenas 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ísticaFrom. Se uma função retornarResult<T, MyError>e?encontrario::Error, desde queFrom<io::Error> for MyErroresteja implementado,?chamará automaticamenteFrom::frompara realizar a conversão. É também por isso que o cratethiserrorpode 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 oferecestd::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 usecatch_unwindcomo 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
DisplayquantoDebug? R: Porque o traitErrordo Rust exige ambos.Displaycontrola as mensagens de erro exibidas aos usuários ({}), enquantoDebugcontrola as informações de depuração ({:?}). A traitErrorda biblioteca padrão exige que um tipo implementeimpl std::error::Error for MyError {}somente se ele implementarDisplay + Debug. Em projetos reais, você pode usarthiserrorpara derivar automaticamente essas implementações.
📖 Resumo
Result<T, E>está no centro do tratamento de erros do Rust —Ok(T)indica sucesso,Err(E)indica falha, e o compilador exige que você trate ambos os casos- O
?operador é um sintaxe simplificada para a propagação de erros — ele retorna automaticamenteErrem caso de falha, recupera o valor deOkem caso de sucesso e realiza automaticamente a conversão de tipos de erro. match/unwrap_or/unwrap_or_elseoferecem tratamento de erros em diferentes níveis de granularidade — desde ramificações detalhadas até um retorno rápido ao plano alternativo- Tipos de erro personalizados: Torne as mensagens de erro claras e legíveis implementando
Display + Debuge useFrom traitpara realizar a conversão de tipos. panic!é usado para erros irrecuperáveis (bugs no programa),Resulté usado para erros recuperáveis (exceções do ambiente externo) — essa é a estratégia básica de tratamento de erros no Rustanyhowethiserrorsão ferramentas padrão para o tratamento de erros em ambiente de produção — a primeira simplifica a propagação, enquanto a segunda simplifica a definição
📝 Exercícios
-
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. Emmain, usematchpara lidar com três cenários: uma idade válida, uma entrada não numérica e um valor fora do intervalo. -
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 personalizadoUserDataError(definido por meio de uma enumeração), que inclui três variantes:FileNotFound,ParseFailed(String)eInvalidAge(i32). -
Dificuldade ⭐⭐⭐: Projete uma minicalculadora que suporte quatro operações:
add,subtract,multiplyedivide. 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 comocalculate("10 + 5 * 2") - Dica: Primeiro, divida a sequência de caracteres por espaços e, em seguida, processe cada parte individualmente usando
foldou um loop.
- Definir a enumeração