2.1 La tarea ausente
Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme - lo agradezco.
Un resultado negativo sigue siendo un resultado - como todo el mundo sabe. Es fácil olvidarlo, sobre todo si eres una herramienta. Una herramienta que no puede informar de ello vuelca el banco de trabajo. Desde fuera, la diferencia entre “no encontrado” y “roto” es obvia. Desde dentro - en absoluto.
Lo que necesitas
tq puede añadir tareas, recorrerlas y marcarlas como completadas. Todavía no puede
encontrar una tarea específica por su identificador. Se puede acceder a la lista directamente:
tasks[0], tasks[1]. Pero eso es un índice, no un id - y si la entrada pedida no está
ahí, la lista vuelca el banco de trabajo.
Crea una función get: recibe una lista y un identificador, y devuelve la tarea - o informa
honestamente de que no está.
La construcción
Rust tiene un tipo para esto: Option<T> - dos variantes:
Some(value)- el valor está presenteNone- el valor está ausente
Option<&Task> y &Task son tipos distintos. Para llegar a la tarea dentro, ambos casos
deben manejarse explícitamente. Con este tipo de retorno, ignorar accidentalmente la ausencia
no es posible.
fn get(tasks: &[Task], id: u64) -> Option<&Task> {
for task in tasks {
if task.id == id {
return Some(task);
}
}
None
}
La forma anterior está expandida para que Some y None sean visibles donde aparecen.
La versión de trabajo es más corta:
fn get(tasks: &[Task], id: u64) -> Option<&Task> {
tasks.iter().find(|task| task.id == id)
}
Para el compilador ambas formas son idénticas: find puede leerse como el bucle de arriba
- la misma iteración, la misma condición, el mismo resultado.
Conviene acostumbrarse a usar iteradores en lugar de for desde ahora. En este caso
concreto la diferencia es solo de tamaño - la verdadera fuerza aparece en las cadenas:
.filter(), .map(), .find() uno tras otro sin bucles anidados. Cuando lleguemos a ellas
- la mano ya sabrá dónde ir.
|task| task.id == ides un cierre, tema aparte.
La llamada pasa por match - cada variante recibe su propio camino:
match get(&tasks, 1) {
Some(task) => println!("#{}: {}", task.id, task.title),
None => println!("not found"),
}
Dos tests - dos caminos:
#[test]
fn get_finds_existing_task() {
let mut tasks = Vec::new();
add(&mut tasks, Task::new(1, "Buy coffee"));
assert!(get(&tasks, 1).is_some());
}
#[test]
fn get_returns_none_for_absent_task() {
let tasks: Vec<Task> = Vec::new();
assert!(get(&tasks, 99).is_none());
}
La suite completa de tests está en tq/.
El resultado
El main de abajo muestra solo lo nuevo - dos llamadas a get. El código completo con
list y todo lo acumulado hasta ahora está en tq/.
fn main() {
let mut tasks: Vec<Task> = Vec::new();
add(&mut tasks, Task::new(1, "Buy coffee"));
add(&mut tasks, Task::new(2, "Buy milk"));
add(&mut tasks, Task::new(3, "Buy eggs"));
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"),
}
}
#1: Buy coffee
not found
make ci pasa. get devuelve una tarea o su ausencia - el código que llama decide qué
hacer con cada una.
El código completo de
tqpara este capítulo está en2-honest/01-the-absent-task/.
Lore: if let
match requiere manejar ambas variantes. Cuando solo se necesita una, if let es más corto:
if let Some(task) = get(&tasks, 1) {
println!("#{}: {}", task.id, task.title);
}
Si la tarea no se encuentra, no ocurre nada. Para los casos en que la ausencia no requiere
ninguna acción separada, esto es más preciso que un match con None => {} vacío.