2.1 Отсутствующая задача
Как известно отрицательный результат тоже результат. Об этом легко забыть, особенно если ты инструмент. Инструмент, который не умеет его дать, опрокидывает верстак. Снаружи разница между “не нашёл” и “сломался” очевидна. Изнутри - совершенно нет.
Что нужно
tq умеет добавлять задачи, перебирать их, отмечать выполненными. Навыка найти одну конкретную
по идентификатору пока нет. Можно обратиться к списку напрямую: tasks[0], tasks[1].
Но это индекс, а не идентификатор (id) - и если запрошенного нет, список опрокидывает верстак.
Создадим функцию get: принимает список и идентификатор, возвращает задачу - или честно
сообщает, что её нет.
Сборка
В Rust для этого есть тип Option<T> - два варианта:
Some(value)- значение естьNone- значения нет
Option<&Task> и &Task - разные типы. Чтобы добраться до задачи внутри, нужно явно
разобрать оба случая. После такого определения результата
случайно проигнорировать отсутствие не получится.
fn get(tasks: &[Task], id: u64) -> Option<&Task> {
for task in tasks {
if task.id == id {
return Some(task);
}
}
None
}
Вызов - через match: каждый вариант получает свой путь:
match get(&tasks, 1) {
Some(task) => println!("#{}: {}", task.id, task.title),
None => println!("not found"),
}
Два теста - два пути:
#[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());
}
Полный набор тестов - в tq/.
Итог
main ниже показывает только новое - два вызова get. Полный код с list и остальным
накопленным кодом - в 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 проходит. get возвращает либо задачу, либо её отсутствие - вызывающий код
решает, что с этим делать.
Полный код
tqдля этой главы -2-honest/01-the-absent-task/.
Lore: iter().find()
Функция get реализует стандартный поиск: перебрать, проверить условие, вернуть первый
подходящий. Это же можно реализовать с помощью iter().find():
fn get(tasks: &[Task], id: u64) -> Option<&Task> {
tasks.iter().find(|task| task.id == id)
}
Option здесь тот же - find возвращает Some(&Task) если нашёл, None если нет.
В тексте - развёрнутая, чтобы Some и None были видны явно.
Однако в коде в tq/ использует эту форму не только из эстетических соображений: clippy знает линт
manual_find и отклоняет развёрнутый цикл. make ci не пропустит вариант из текста
без изменений - это намеренное расхождение между иллюстрацией и рабочим кодом.
Lore: if let
match требует обработки обоих вариантов. Когда нужен только один - if let короче:
if let Some(task) = get(&tasks, 1) {
println!("#{}: {}", task.id, task.title);
}
Если задача не нашлась - ничего не происходит. Для случаев, когда отсутствие не требует
отдельного действия, это точнее, чем match с пустым None => {}.