2.3 Pasar el problema hacia arriba
Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme — lo agradezco.
Un martillo no decide si la madera es adecuada. Su asunto es el clavo; todo lo demás es responsabilidad del carpintero que sostiene el mango. Una herramienta que toma decisiones en nombre de quien la sostiene trabaja en silencio - y al hacerlo se apropia del contexto ajeno. El contexto ajeno tiene la costumbre de resultar importante exactamente cuando nunca se pidió.
El intento
En el capítulo anterior .unwrap() aparecía en cada llamada a Task::new en main: “los
títulos de las tareas se conocen de antemano, no habrá errores.” Mientras los datos vienen
del código, esto es honesto. En el momento en que empiecen a llegar del exterior, “conocidos
de antemano” dejará de ser verdad.
El primer paso es eliminar .unwrap() y pasar el error hacia arriba. Rust tiene el operador
? para esto. Prueba a reemplazar una llamada:
fn main() {
let mut tasks: Vec<Task> = Vec::new();
add(&mut tasks, Task::new(1, "Buy coffee")?);
El compilador lo nota:
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option`
(or another type that implements `FromResidual`)
--> src/main.rs:XX:XX
|
XX | fn main() {
| --------- this function should return `Result` or `Option` to accept `?`
...
XX | add(&mut tasks, Task::new(1, "Buy coffee")?);
| ^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `FromResidual<Result<Infallible, TqError>>` is not implemented for `()`
main devuelve (). El operador ? necesita otra cosa.
Lo que sabe el compilador
? es una abreviatura para manejar un Result. El código:
Task::new(1, "Buy coffee")?
se expande a algo como:
match Task::new(1, "Buy coffee") {
Ok(task) => task,
Err(e) => return Err(e),
}
Si Ok(task) - continúa, el valor está disponible. Si Err(e) - devuelve el error al
llamador inmediatamente. Por eso exactamente ? requiere que la función actual devuelva un
Result: necesita un lugar adonde enviar el error.
En Rust, main puede devolver Result<(), E> siempre que E implemente Debug. Cambia
la firma:
fn main() -> Result<(), TqError> {
Ahora ? tiene adonde reenviar los errores. Si main termina con Err, Rust imprimirá el
valor a través de Debug y saldrá con un código no cero.
El conjuro
Reemplaza .unwrap() por ? en cada llamada a Task::new, y añade un éxito explícito al
final:
fn main() -> Result<(), TqError> {
let mut tasks: Vec<Task> = Vec::new();
add(&mut tasks, Task::new(1, "Buy coffee")?);
list(&tasks);
add(&mut tasks, Task::new(2, "Buy milk")?);
add(&mut tasks, Task::new(3, "Buy eggs")?);
let task = &mut tasks[0];
task.complete();
if task.is_done() {
println!("Task 1 done");
}
list(&tasks[1..]);
match get(&tasks, 1) {
Some(task) => println!("#{}: {}", task.id, task.title),
None => println!("not found"),
}
match get(&tasks, 99) {
Some(task) => println!("#{}: {}", task.id, task.title),
None => println!("not found"),
}
Ok(())
}
Ok(()) al final es el éxito explícito. Sin él, una función con tipo de retorno
Result<(), TqError> no compilaría: el cuerpo debe devolver un Result, no ().
La lógica de Task::new no ha cambiado - los tests del capítulo anterior en tq/ siguen
pasando.
make ci pasa. Los errores de Task::new ya no se resuelven en el sitio - viajan hacia
arriba, a quien sabe qué hacer con ellos.
El código completo de
tqpara este capítulo está en2-honest/03-passing-the-problem-upward/.
Lore: .unwrap(), ? y match explícito
Tres formas de trabajar con un Result:
-
.unwrap()- “estoy seguro de que aquí hay unOk. Si no - que el programa falle.” Adecuado en tests y en código con datos conocidos como válidos. -
?- “si hay un error, lo paso hacia arriba.” Solo funciona en funciones que devuelvenResult. Elimina el ruido, preserva el significado. -
match- “manejo ambos casos aquí.” Necesario cuando el error requiere un comportamiento específico: registrarlo, sustituir un valor por defecto, reintentar.
En main, la cadena termina - el error no tiene adónde ir más. Por eso ? se usa a menudo
en llamadas intermedias, y a main simplemente se le permite devolver Err, imprimiéndolo
a través de Debug.