2.2 Cuando las cosas salen mal
Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme — lo agradezco.
Decir “no encontrado” es una habilidad. Decir “no acepto esto, y aquí está el motivo” es otra. Para un artesano la diferencia es obvia; para la mayoría de herramientas no lo es en absoluto. Una herramienta que solo puede soltar una pieza en silencio cuando resulta ser defectuosa no es mucho más honesta que una que simplemente falla. La diferencia es solo en quién escucha el golpe.
Lo que necesitas
Task::new(1, "") crea una tarea sin título y no pone ninguna objeción. Después aparecerá
en la lista, ocupará su lugar, y no habrá forma de saber de dónde vino ni qué significa. Se
necesita una forma de decir en la entrada: esta tarea no es aceptable - y explicar por qué.
Se necesita un tipo que lleve un valor o un motivo de rechazo - y que deje la decisión al código que llama.
La construcción
Empieza con el error. TqError es una enumeración de motivos de rechazo. Empieza con uno:
#[derive(Debug)]
enum TqError {
EmptyTitle,
}
Result<T, E> es el tipo estándar de Rust para operaciones que pueden tener éxito o fallar:
Ok(value)- la operación tuvo éxito; el resultado está dentroErr(reason)- la operación falló; el motivo está dentro
Cambia Task::new. Antes: -> Task. Después: -> Result<Task, TqError>:
fn new(id: u64, title: &str) -> Result<Task, TqError> {
if title.is_empty() {
return Err(TqError::EmptyTitle);
}
Ok(Task {
id,
title: String::from(title),
status: Status::Todo,
})
}
Si el título está vacío, devuelve Err(TqError::EmptyTitle). Si no, envuelve la tarea en
Ok(...). Ambas variantes tienen el mismo tipo: Result<Task, TqError>.
Ahora quien llame a Task::new recibe no una tarea sino un Result - y debe manejar ambos
casos explícitamente. Ignorar el error en silencio ya no es posible.
Donde se sabe que una tarea es válida, se puede usar .unwrap() por ahora:
add(&mut tasks, Task::new(1, "Buy coffee").unwrap());
Donde un error es posible, manéjalo explícitamente:
match Task::new(4, "") {
Ok(_) => println!("task created"),
Err(e) => println!("error: {:?}", e),
}
Dos variantes - dos tests:
#[test]
fn new_task_rejects_empty_title() {
assert!(Task::new(1, "").is_err());
}
#[test]
fn new_task_accepts_valid_title() {
assert!(Task::new(1, "Buy coffee").is_ok());
}
La suite completa de tests está en tq/.
El resultado
El main de abajo muestra solo lo nuevo - .unwrap() en tareas válidas y manejo explícito
de errores. El código completo acumulado está en tq/.
fn main() {
let mut tasks: Vec<Task> = Vec::new();
add(&mut tasks, Task::new(1, "Buy coffee").unwrap());
add(&mut tasks, Task::new(2, "Buy milk").unwrap());
add(&mut tasks, Task::new(3, "Buy eggs").unwrap());
match Task::new(4, "") {
Ok(_) => println!("task created"),
Err(e) => println!("error: {:?}", e),
}
}
error: EmptyTitle
make ci pasa. Task::new ahora rechaza aceptar un título vacío - y explica por qué.
El código completo de
tqpara este capítulo está en2-honest/02-when-things-go-wrong/.
Lore: .unwrap() y cuándo usarlo
.unwrap() es una elección explícita: “estoy seguro de que aquí hay un Ok, y si no -
que el programa falle.” En el código de arriba esto está justificado: los títulos de las
tareas se conocen de antemano y definitivamente no están vacíos. En código que acepta datos
del exterior, .unwrap() es un riesgo. El capítulo 2.3 presentará el operador ?, que
elimina la necesidad de esta elección en la mayoría de los casos.