← Índice

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 tq para este capítulo está en 2-honest/03-passing-the-problem-upward/.


Lore: .unwrap(), ? y match explícito

Tres formas de trabajar con un Result:

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.