← Índice

2.4 Un nombre para lo ausente

Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme — lo agradezco.

Un mensajero que vuelve con las manos vacías es útil exactamente en la medida en que puede explicar qué no encontró. “No lo encontré” cierra la conversación. “No encontré esta cosa concreta” abre la siguiente. La mayoría de herramientas prefieren lo primero. Esto suele llamarse brevedad, aunque la palabra exacta es algo diferente.

Lo que necesitas

get devuelve Option<&Task>. En el capítulo 2.1 era la elección correcta: una tarea o existe o no, no hay motivos para su ausencia - solo el hecho. Desde entonces tq ha cambiado. Los errores ahora tienen tipos; las funciones comparten un lenguaje común: Result.

Option tiene dos limitaciones en el contexto actual.

La primera: ? no funciona con él. main devuelve Result<(), TqError>, e intentar aplicar ? a un Option<&Task> produce un error del compilador: Option y Result son tipos distintos.

La segunda: la ausencia es anónima. None dice “no.” No dice “no hay ninguna tarea con id 42.” Cuando un error viaja hacia arriba, el llamador pierde contexto: ¿qué tarea concretamente no se encontró?

TqError puede llevar información. Añade una variante NotFound(u64) - contendrá el id de la tarea. get debe devolver exactamente eso.

La construcción

Primero, añade la nueva variante de error:

#[derive(Debug)]
enum TqError {
    EmptyTitle,
    NotFound(u64),
}

Al imprimirse para depuración, esto tiene el aspecto NotFound(42) - específico e inequívoco.

Ahora get. La versión anterior devolvía Option<&Task> a través de find. La nueva usa .ok_or(...) - un método de Option que convierte None en Err(e) y Some(v) en Ok(v):

fn get(tasks: &[Task], id: u64) -> Result<&Task, TqError> {
    tasks.iter().find(|task| task.id == id).ok_or(TqError::NotFound(id))
}

find busca; .ok_or(...) envuelve la ausencia en un error con nombre que lleva el id.

En main, ambas llamadas a get usan match. Los patrones cambian de Some/None a Ok/Err:

match get(&tasks, 1) {
    Ok(task) => println!("found: #{}: {}", task.id, task.title),
    Err(e) => println!("error: {:?}", e),
}

match get(&tasks, 99) {
    Ok(task) => println!("#{}: {}", task.id, task.title),
    Err(TqError::NotFound(id)) => println!("task {} not found", id),
    Err(e) => println!("error: {:?}", e),
}

El segundo match desestructura NotFound(id) - extrae el id del error y lo usa directamente.

El brazo Err(e) en este match es actualmente inalcanzable: get solo produce NotFound, no tiene otros errores. Pero el compilador ve TqError como un todo y exige que todas las variantes estén cubiertas. Este capturador general es un patrón aceptado en Rust: cuando TqError crezca y aparezcan nuevas variantes, este brazo las capturará sin ningún cambio en el código.

El resultado

cargo run ahora imprime:

task 99 not found

No “not found” - “task 99 not found”. El mensajero nombró lo que no encontró.

? ahora funciona con get. Una función que busca una tarea y quiere pasar el error hacia arriba puede escribir get(&tasks, id)? - y NotFound(id) viajará al llamador sin un match adicional.

Un nuevo test para este cambio - comprueba que el id se conserva en el error:

#[test]
fn get_carries_missing_id_in_error() {
    let tasks: Vec<Task> = Vec::new();
    assert!(matches!(get(&tasks, 99), Err(TqError::NotFound(99))));
}

matches!(expr, pattern) devuelve true si la expresión coincide con el patrón - incluyendo valores anidados. La suite completa de tests está en tq/.

make ci pasa. get ya no guarda silencio sobre qué estaba buscando.

El código completo de tq para este capítulo está en 2-honest/04-a-name-for-the-absent/.


Lore: Option y Result - dos formas de decir no

Option dice: un valor o está presente o está ausente. Sin detalles - solo el hecho. Úsalo cuando la ausencia es un resultado normal que no requiere explicación. Vec::first() devuelve Option<&T>: un vector vacío no es un error, solo un estado.

Result dice: una operación tuvo éxito o falló - y si falló, aquí está el motivo. Úsalo cuando el llamador necesita saber la razón.

Una regla práctica: si None es un resultado normal de trabajo, usa Option. Si None significa “algo salió mal”, usa Result.