← Í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 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: iter().find()

La función get implementa una búsqueda estándar: iterar, comprobar la condición, devolver la primera coincidencia. Lo mismo puede escribirse con iter().find():

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

El Option es el mismo - find devuelve Some(&Task) si encuentra, None si no. La forma expandida se usa en el texto para que Some y None sean visibles explícitamente. El código en tq/ usa esta forma - no solo por razones estéticas: clippy conoce el lint manual_find y rechaza el bucle expandido. make ci no pasará la versión del texto sin cambios; esta es una divergencia intencionada entre la ilustración y el código funcional.


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.