← Índice

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:

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

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 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 tq para este capítulo está en 2-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.