Как прервать итерацию и вернуть ошибку, если Iterator::map возвращает Result::Err?
Проблема с collect и ранним прерыванием
Ваш текущий код с collect на самом деле уже прерывает итерацию при первой ошибке, если тип результата — Result. Это работает благодаря тому, что collect для Iterator реализован особым образом: он вызывает Try::branch на каждом элементе и при встрече Err немедленно останавливает итерацию, возвращая эту ошибку. Однако, если вы хотите явно контролировать процесс или использовать другие методы, есть альтернативы.
Решение с try_for_each
Метод try_for_each идеально подходит для вашей задачи. Он вызывает замыкание для каждого элемента и останавливается при первой ошибке. Вот пример:
use std::num::ParseIntError;
fn parse_numbers(input: &[&str]) -> Result<Vec<i32>, ParseIntError> {
let mut result = Vec::new();
input.iter().try_for_each(|s| {
let num = s.parse::<i32>()?; // ? немедленно вернёт Err
result.push(num);
Ok(())
})?;
Ok(result)
}
Здесь try_for_each принимает замыкание, возвращающее Result<(), ParseIntError>. Если парсинг не удался, оператор ? возвращает Err из замыкания, и try_for_each немедленно завершается. Затем мы используем ? на результате try_for_each, чтобы передать ошибку наверх.
Решение с циклом for и ранним выходом
Самый простой и читаемый способ — использовать обычный цикл for с ранним выходом:
fn parse_numbers(input: &[&str]) -> Result<Vec<i32>, ParseIntError> {
let mut result = Vec::new();
for s in input {
let num = s.parse::<i32>()?; // при ошибке сразу возвращаем Err
result.push(num);
}
Ok(result)
}
Этот подход предпочтительнее, если логика обработки сложнее простого маппинга. Он явно показывает, что итерация прерывается при ошибке, и легко поддерживается.
Решение с filter_map (не подходит)
Метод filter_map отфильтровывает None и Err, что не подходит, так как вы хотите вернуть ошибку, а не игнорировать её. Используйте его только если нужно пропускать неудачные элементы без остановки.
Почему collect работает?
Как упоминалось, collect для Result использует типаж FromIterator, который реализован через Try. Это означает, что:
- При встрече
Ok(T)элемент добавляется в вектор. - При встрече
Err(E)итерация немедленно прекращается, и ошибка возвращается.
Поэтому ваш исходный код уже работает корректно и прерывается при первой ошибке. Но если вы хотите явно видеть логику прерывания, используйте цикл или try_for_each.
Рекомендация
Для простого случая используйте collect — это идиоматично и кратко. Если нужна дополнительная логика (например, логирование или преобразование ошибки), предпочтите цикл for или try_for_each. Избегайте filter_map, если не хотите терять ошибки.
Альтернативный подход: использование метода fold с ранним выходом
Метод fold позволяет аккумулировать результат с контролем прерывания. Хотя fold сам по себе не поддерживает досрочный выход, вы можете использовать Result как аккумулятор и оператор ? для передачи ошибки:
use std::num::ParseIntError;
fn parse_numbers(input: &[&str]) -> Result<Vec<i32>, ParseIntError> {
input.iter().fold(Ok(Vec::new()), |acc, s| {
let mut vec = acc?; // если была ошибка, возвращаем её
let num = s.parse::<i32>()?;
vec.push(num);
Ok(vec)
})
}
Здесь fold проходит по всем элементам, но при первой ошибке acc? вернёт Err, и последующие итерации будут работать с этим Err, но формально fold не прерывается — он просто передаёт ошибку дальше. Это менее эффективно, чем try_for_each, но может быть полезно, если вам нужно, например, собрать все ошибки (тогда используйте Result<Vec<T>, Vec<E>>).
Для вашей задачи лучше использовать try_for_each или цикл for, так как они явно прерывают итерацию при ошибке и более читаемы.
Ответов пока нет.
Войдите чтобы оставить ответ