Как остановить итерацию и вернуть ошибку, когда Iterator::map возвращает Result::Err?
Решение проблемы: использование collect с Result
Ваша интуиция была верна — collect действительно умеет собирать Iterator в Result, но только если явно указать тип. Проблема в том, что Rust не может автоматически вывести, что вы хотите Result, а не Vec. Нужно помочь компилятору с помощью турбо-рыбы или явной аннотации.
use std::num::ParseIntError;
fn parse_numbers(input: Vec<&str>) -> Result<Vec<i32>, ParseIntError> {
input.iter()
.map(|s| s.parse::<i32>())
.collect() // Ошибка: неоднозначный тип
}
Ошибка возникает, потому что collect может собрать как Vec<Result<i32, ParseIntError>>, так и Result<Vec<i32>, ParseIntError>. Решение — уточнить тип:
fn parse_numbers(input: Vec<&str>) -> Result<Vec<i32>, ParseIntError> {
input.iter()
.map(|s| s.parse::<i32>())
.collect::<Result<Vec<_>, _>>() // Турбо-рыба!
}
Как это работает под капотом
Стандартная библиотека реализует трейт FromIterator для Result. Когда вы пишете .collect::<Result<Vec<T>, E>>(), итератор вызывает FromIterator::from_iter, который:
- Создаёт пустой
Vec. - Проходит по каждому элементу итератора.
- Если встречает
Ok(value)— добавляет значение в вектор. - Если встречает
Err(e)— немедленно прекращает итерацию и возвращаетErr(e).
Таким образом, итерация останавливается на первой же ошибке, и вы получаете Err без паники.
Полный рабочий пример
use std::num::ParseIntError;
fn parse_numbers(input: Vec<&str>) -> Result<Vec<i32>, ParseIntError> {
input.iter()
.map(|s| s.parse::<i32>())
.collect::<Result<Vec<_>, _>>()
}
fn main() {
let valid = vec!["1", "2", "3"];
let invalid = vec!["1", "abc", "3"];
match parse_numbers(valid) {
Ok(nums) => println!("Успех: {:?}", nums),
Err(e) => println!("Ошибка: {}", e),
}
match parse_numbers(invalid) {
Ok(nums) => println!("Успех: {:?}", nums),
Err(e) => println!("Ошибка: {}", e), // Выведет: Ошибка: invalid digit found in string
}
}
Альтернативы с явной обработкой
Если вам нужно больше контроля (например, логировать каждую ошибку перед остановкой), можно использовать цикл for с ? оператором:
fn parse_numbers(input: Vec<&str>) -> Result<Vec<i32>, ParseIntError> {
let mut result = Vec::with_capacity(input.len());
for s in &input {
let num = s.parse::<i32>()?; // ? пробрасывает ошибку наверх
result.push(num);
}
Ok(result)
}
Этот подход даёт ту же семантику "остановиться при первой ошибке", но позволяет вставить дополнительную логику (например, eprintln! или проверки).
Важно: Если вы хотите собрать все ошибки, а не только первую, используйте partition или крейт itertools с методом partition_map, но это уже выходит за рамки стандартной библиотеки.
Дополнительный подход: использование try_for_each и try_fold
Помимо collect, у итераторов есть методы try_for_each и try_fold, которые специально созданы для работы с Result и Option. Они останавливают итерацию при первой же ошибке.
use std::num::ParseIntError;
fn parse_numbers(input: Vec<&str>) -> Result<Vec<i32>, ParseIntError> {
let mut result = Vec::with_capacity(input.len());
input.iter().try_for_each(|s| {
let num = s.parse::<i32>()?;
result.push(num);
Ok(())
})?;
Ok(result)
}
Метод try_for_each принимает замыкание, которое возвращает Result. Если замыкание возвращает Err, итерация прекращается, и ошибка пробрасывается. Этот способ чуть более явный, чем collect, и может быть полезен, когда нужно выполнить побочные действия (например, логирование) перед остановкой.
Также есть try_fold для аккумуляции с ранним выходом:
fn parse_numbers(input: Vec<&str>) -> Result<Vec<i32>, ParseIntError> {
input.iter().try_fold(Vec::new(), |mut acc, s| {
let num = s.parse::<i32>()?;
acc.push(num);
Ok(acc)
})
}
Оба метода эквивалентны по производительности, но try_fold более функциональный и не требует мутабельной переменной вне замыкания. Выбирайте тот, который лучше читается в вашем контексте.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги