Unwrap vs Expect
// Manejando el error con expect o unwrap
let option: u8 = option.trim().parse().expect("Invalid input");
let option: u8 = option.trim().parse().unwrap();
// Sin utilizar expect o unwrap para controlar el error
let option: u8 = match option.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid option. Press Enter to continue.");
io::stdin().read_line(&mut String::new()).unwrap();
continue;
}
};
Ambos
- Se utilizan para el manejo de errores.
- Intentan obtener el valor resultante de una operación.
- Si la operación es exitosa, devuelven el valor dentro de
Ok. - Si la operación falla (retorna
Err) que podría detener el programa, provocandopanic. - La diferencia más notoria es que Unwrap muestra un mensaje de error genérico y Expect muestra un mensaje de error personalizado.
- En código de producción generalmente se prefiere manejar errores explícitamente en lugar de usar
unwrap/expect.
unwrap()
- Muestra un mensaje de error genérico proporcionado por Rust.
- Simplemente intenta obtener el valor y, si no puede, provoca un
paniccon un mensaje de error estándar. - Se utiliza en situaciones donde se está seguro (o casi seguro) de que la operación siempre será exitosa y no se necesita un mensaje de error personalizado.
- También se usa frecuentemente en pruebas unitarias donde un pánico es aceptable si la prueba falla.
- A menudo se considera menos ideal en código de producción porque no proporciona contexto adicional sobre el error.
Variantes
unwrap()básico paraResultyOptionunwrap_or(default)- devuelve el valor o un valor predeterminadounwrap_or_else(closure)- devuelve el valor o ejecuta una función/closureunwrap_or_default()- devuelve el valor o el valor predeterminado del tipounwrap_err()- paraResult, similar aexpect_errpero sin mensaje personalizadounwrap_unchecked()- versión unsafe que asume que el valor existe (no comprueba)
expect()
- Permite mostrar un mensaje personalizado cuando se produce un
panic. - Es preferible en situaciones donde se requiere un mensaje de error más claro y específico, lo que facilita la depuración.
- Es especialmente útil para depuración o cuando se maneja la entrada del usuario, o cualquier otra situación donde el fallo podría ser anticipado.
- Sugiere que se está anticipando una posible falla y se proporciona información adicional sobre el contexto del error.
Variantes
- El método básico
expect(msg)paraResultyOption expect_err(msg)paraResult(permite obtener el error esperando que elResultseaErr)
Manejando el error
Con match
loop {
let option: u8 = match option.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid option. Press Enter to continue.");
io::stdin().read_line(&mut String::new()).unwrap();
continue;
}
}
}
Operador ?
- El operador
?es una forma concisa de propagar errores en Rust - El operador
?solo se puede usar en funciones que retornan unResulto unOption. - Si el
ResultesOk(v), extrae el valorv - Si el
ResultesErr(e), retorna inmediatamente de la función con ese error - Es básicamente equivalente a escribir un
matchpara manejar el error, pero mucho más compacto - Se usa también en producción
// Con el operador ?
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("username.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
// Equivalente usando match
fn read_username_from_file_verbose() -> Result<String, io::Error> {
let file = match File::open("username.txt") {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
Ejemplo 2
- Service
service
fn get_user_service(user_id: u64) -> Result<User, ServiceError> {
// Propaga el error de la DB si ocurre
let user = database.find_user(user_id)?;
if !user.is_active {
return Err(ServiceError::NotFound("Usuario inactivo".to_string()));
}
Ok(user)
}
- Controller
controller
fn get_user_controller(req: Request) -> Result<Response, ControllerError> {
let user_id = req.params.get("id").ok_or(ControllerError::BadRequest)?;
let user_id = user_id.parse::<u64>().map_err(|_| ControllerError::BadRequest)?;
// Aquí usamos ? para propagar el error del service
// ServiceError se convierte en ControllerError
let user = get_user_service(user_id)?;
Ok(Response::json(user))
}
Propagación con transformación: Para que el ? funcione entre capas con diferentes tipos de error (ServiceError → ControllerError), se necesita implementar el trait From<ServiceError> for ControllerError. Esto permite la conversión automática al propagar errores.
Manejo explícito de errores
En código de producción, es mejor manejar los errores de forma explícita en vez de causar un pánico.
- Devuelve errores como valores (
Result) en lugar de causar pánicos - Proporciona mensajes de error descriptivos
- Permite recuperarse de los errores y continuar la ejecución
- Hace que el código sea más robusto y predecible
use std::io;
fn get_user_option() -> Result<u8, String> {
let mut input = String::new();
// Manejar error de lectura
if let Err(e) = io::stdin().read_line(&mut input) {
return Err(format!("Error de entrada: {}", e));
}
// Manejar error de parseo
match input.trim().parse::<u8>() {
Ok(num) => {
if num > 0 && num <= 5 {
Ok(num)
} else {
Err(format!("Número fuera de rango: {}", num))
}
}
Err(_) => Err(String::from("Por favor ingrese un número válido")),
}
}
fn main() {
println!("Seleccione una opción (1-5):");
match get_user_option() {
Ok(option) => println!("Opción seleccionada: {}", option),
Err(e) => {
println!("Error: {}", e);
// Código de recuperación en lugar de un pánico
println!("Usando opción predeterminada: 1");
// Continuar con la opción predeterminada...
}
}
}