Прийнята відповідь показує, як зупинитися на помилці під час збору , і це добре, тому що саме про це вимагав ОП. Якщо вам потрібна обробка, яка також працює на великих або нескінченних помилкових ітераторах, читайте далі.
Як уже зазначалося, for
може використовуватися для емуляції зупинки при помилці, але це іноді неелегантно, як коли ви хочете зателефонувати max()
або іншим споживаючим способом. В інших ситуаціях це майже неможливо, як, наприклад, коли метод споживання знаходиться в іншому ящику, наприклад, itertools
або Rayon 1 .
Споживач ітератора: try_for_each
Коли ви контролюєте, як ітератор споживається, ви можете просто використовувати, try_for_each
щоб зупинити першу помилку. Він поверне результат, який, Ok
якщо помилки не було, а в Err
іншому випадку містить значення помилки:
use std::{io, fs};
fn main() -> io::Result<()> {
fs::read_dir("/")?
.take_while(Result::is_ok)
.map(Result::unwrap)
.try_for_each(|e| -> io::Result<()> {
println!("{}", e.path().display());
Ok(())
})?;
Ok(())
}
Якщо вам потрібно підтримувати стан між викликами закриття, ви можете також використовувати try_fold
. Обидва методи реалізовані ParallelIterator
, тому ви можете використовувати їх із Rayon.
Цей підхід вимагає контролювати споживання ітератора. Якщо це робиться за допомогою коду, який не знаходиться під вашим контролем - наприклад, якщо ви передаєте ітератор itertools::merge()
або тому подібне, вам знадобиться адаптер.
Адаптер ітератора: scan
Перша спроба зупинити помилку полягає у використанні take_while
:
use std::{io, fs};
fn main() -> io::Result<()> {
fs::read_dir("/")?
.take_while(Result::is_ok)
.map(Result::unwrap)
.for_each(|e| println!("{}", e.path().display()));
Ok(())
}
Це працює, але ми не отримуємо жодних ознак того, що сталася помилка, ітерація просто мовчки зупиняється. Крім того, для цього потрібна неприваблива річ, через map(Result::unwrap)
яку здається, що програма буде панікувати через помилку, що насправді не так, оскільки ми зупиняємось на помилці.
Обидві проблеми можна виправити, переключившись з take_while
на scan
, більш потужний комбінатор, який не тільки підтримує зупинку ітерації, але передає свої елементи, що належать зворотному виклику, дозволяючи закриттю витягнути помилку абоненту:
fn main() -> io::Result<()> {
let mut err = Ok(());
fs::read_dir("/")?
.scan(&mut err, |err, res| match res {
Ok(o) => Some(o),
Err(e) => {
**err = Err(e);
None
}
})
.for_each(|e| println!("{}", e.path().display()));
err?;
Ok(())
}
Якщо це потрібно в кількох місцях, закриття можна абстрагувати до функції утиліти:
fn until_err<T, E>(err: &mut &mut Result<(), E>, item: Result<T, E>) -> Option<T> {
match item {
Ok(item) => Some(item),
Err(e) => {
**err = Err(e);
None
}
}
}
... в цьому випадку ми можемо викликати це як .scan(&mut err, until_err)
( дитячий майданчик ).
Ці приклади тривіально виснажують ітератор for_each()
, але його можна зв'язати довільними маніпуляціями, включаючи район par_bridge()
. Використовуючи scan()
його, можна навіть переносити collect()
елементи в контейнер і мати доступ до елементів, побачених до помилки, що іноді є корисним і недоступним під час збирання в Result<Container, Error>
.
1 Потрібно використовувати `par_bridge ()` при використанні Rayon для паралельної обробки потокових даних:
fn process(input: impl BufRead + Send) -> std::Result<Output, Error> {
let mut err = Ok(());
let output = lines
.input()
.scan(&mut err, until_err)
.par_bridge()
.map(|line| ... executed in parallel ... )
.reduce(|item| ... also executed in parallel ...);
err?;
...
Ok(output)
}
Знову ж таки, еквівалентний ефект неможливо тривіально досягти, збираючи в Result
.