intermedio⏱45 minutosLección 3 de 10
panic! y Estrategias de Manejo de Errores
Aprenda cuándo usar panic, cómo propagar errores y construir manejo robusto de errores con anyhow y thiserror
panic! y Estrategias de Manejo de Errores
No todos los errores son recuperables. Rust proporciona panic! para errores irrecuperables y Result para errores recuperables. Elegir la estrategia correcta es clave para un código robusto.
panic! — Errores Irrecuperables
panic! imprime un mensaje de error, desenrolla la pila (por defecto) y termina:
rust
fn main() {
panic!("crash and burn");
// thread 'main' panicked at 'crash and burn', src/main.rs:2:5
}Escenarios Comunes de Panic
rust
fn main() {
// Índice fuera de los límites
let v = vec![1, 2, 3];
// v[10]; // panic: index out of bounds
// Unwrap en None
let x: Option<i32> = None;
// x.unwrap(); // panic: called `Option::unwrap()` on a `None` value
// Desbordamiento de entero (modo debug)
let x: u8 = 255;
// let y = x + 1; // panic: attempt to add with overflow
// Aserción fallida
// assert!(false); // panic: assertion failed
}Warning
Mensajes de Panic Personalizados
rust
fn main() {
let msg = String::from("custom error");
panic!("something went wrong: {msg}");
}panic! Estratégicamente
rust
fn divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
panic!("division by zero");
}
a / b
}
// Mejor: retornar Result
fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("division by zero".to_string())
} else {
Ok(a / b)
}
}Unwrap vs Expect
rust
fn main() {
// unwrap: mínimo, no descriptivo
let result: Result<i32, &str> = Err("error");
// result.unwrap(); // panics with: called `Result::unwrap()` on an `Err` value: "error"
// expect: mensaje descriptivo
// result.expect("failed to get value");
// panics with: failed to get value: "error"
}Note
Propagación de Errores con ?
El operador ? propaga errores al llamante:
rust
use std::fs;
use std::io;
use std::num::ParseIntError;
// Múltiples tipos de error = dolor de cabeza
fn read_and_parse(path: &str) -> Result<i32, ???> {
let content = fs::read_to_string(path)?; // io::Error
let num = content.trim().parse::<i32>()?; // ParseIntError
Ok(num)
}Solución 1: Box<dyn Error>
rust
use std::error::Error;
fn read_and_parse(path: &str) -> Result<i32, Box<dyn Error>> {
let content = fs::read_to_string(path)?;
let num = content.trim().parse::<i32>()?;
Ok(num)
}Success
Box<dyn Error> es la forma más simple de manejar múltiples tipos de error. Es excelente para aplicaciones pero pierde información de tipo.
Solución 2: Tipo de Error Personalizado
rust
use std::fmt;
use std::fs;
use std::io;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(ParseIntError),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "IO error: {e}"),
AppError::Parse(e) => write!(f, "Parse error: {e}"),
}
}
}
impl From<io::Error> for AppError {
fn from(e: io::Error) -> AppError {
AppError::Io(e)
}
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> AppError {
AppError::Parse(e)
}
}
fn read_and_parse(path: &str) -> Result<i32, AppError> {
let content = fs::read_to_string(path)?; // Convertido vía From
let num = content.trim().parse::<i32>()?; // Convertido vía From
Ok(num)
}anyhow — Manejo Ergonométrico de Errores
anyhow proporciona anyhow::Error y el trait Context:
toml
[dependencies]
anyhow = "1.0"rust
use anyhow::{Context, Result};
use std::fs;
fn read_config(path: &str) -> Result<String> {
let content = fs::read_to_string(path)
.with_context(|| format!("failed to read config from {path}"))?;
Ok(content)
}
fn parse_port(content: &str) -> Result<u16> {
content.trim().parse::<u16>()
.with_context(|| format!("invalid port number: {content}"))?
}
fn main() -> Result<()> {
let config = read_config("config.toml")?;
let port = parse_port(&config)?;
println!("Port: {port}");
Ok(())
}Note
thiserror — Tipos de Error para Bibliotecas
thiserror deriva Display y Error para tipos de error personalizados:
toml
[dependencies]
thiserror = "2"rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataError {
#[error("data store disconnected")]
Disconnect(#[from] io::Error),
#[error("invalid header (expected {expected:?}, got {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("unknown data store error")]
Unknown,
}
fn process_data() -> Result<(), DataError> {
// io::Error se convierte automáticamente vía #[from]
let _content = fs::read_to_string("data.txt")?;
Err(DataError::Unknown)
}Success
¿Panic o No?
Cuándo usar panic:
rust
// Violación de invariante — esto nunca debería ocurrir
fn month_name(month: u8) -> &'static str {
match month {
1 => "January",
2 => "February",
// ...
12 => "December",
_ => panic!("invalid month: {month}"), // Bug de programación
}
}
// Unwrap en operaciones infalibles
fn main() {
let parsed: i32 = "42".parse().unwrap(); // Parse siempre funciona aquí
// Mejor: usar expect
let parsed: i32 = "42".parse().expect("hardcoded literal must parse");
}Cuándo NO usar panic:
rust
// Entrada del usuario — nunca usar panic
fn process_user_input(input: &str) -> Result<i32, String> {
input.trim().parse::<i32>().map_err(|_| format!("'{input}' is not a number"))
}Ejemplo Real: Manejo de Errores en un CLI
rust
use anyhow::{Context, Result};
use std::path::Path;
use std::fs;
#[derive(Debug)]
struct UserData {
name: String,
age: u8,
}
fn load_user_data(path: &Path) -> Result<UserData> {
let content = fs::read_to_string(path)
.with_context(|| format!("cannot read {}", path.display()))?;
let lines: Vec<&str> = content.lines().collect();
if lines.len() < 2 {
anyhow::bail!("file must have at least 2 lines");
}
let name = lines[0].to_string();
let age = lines[1].parse::<u8>()
.with_context(|| format!("invalid age: '{}'", lines[1]))?;
Ok(UserData { name, age })
}
fn main() -> Result<()> {
let data = load_user_data(Path::new("user.txt"))?;
println!("Loaded: {} (age {})", data.name, data.age);
Ok(())
}Preguntas de Práctica
- ¿Qué sucede cuando se llama a
panic!? - ¿Cuándo es apropiado usar panic en Rust?
- ¿Cuál es la diferencia entre
unwrap()yexpect()? - ¿Cómo propaga errores el operador
?? - ¿Cuál es la ventaja de
Box<dyn Error>para el manejo de errores? - ¿Cómo mejora
anyhowla ergonomía del manejo de errores? - ¿Qué hace el atributo
#[from]dethiserror? - ¿Cuándo deberías usar
anyhowvsthiserror? - ¿Qué añade
with_context()a un error? - ¿Cómo decides entre un panic y un Result?
Progreso30%