Die akzeptierte Antwort zeigt, wie Sie beim Sammeln auf Fehler verzichten können , und das ist in Ordnung, da das OP dies angefordert hat. Wenn Sie eine Verarbeitung benötigen, die auch mit großen oder unendlich fehlbaren Iteratoren funktioniert, lesen Sie weiter.
Wie bereits erwähnt, for
kann es verwendet werden, um Stop-on-Error zu emulieren, aber das ist manchmal unelegant, wie wenn Sie aufrufen möchten max()
oder eine andere konsumierende Methode. In anderen Situationen ist es fast unmöglich, als wenn die aufwändige Methode ist in einer anderen Kiste, wie itertools
oder Rayon 1 .
Iterator Verbraucher: try_for_each
Wenn Sie steuern, wie der Iterator verwendet wird, können Sie nur den try_for_each
ersten Fehler stoppen. Es wird ein Ergebnis zurückgegeben, das, Ok
wenn kein Fehler aufgetreten ist, Err
andernfalls den Fehlerwert enthält:
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(())
}
Wenn Sie den Status zwischen den Aufrufen des Abschlusses beibehalten müssen, können Sie auch verwenden try_fold
. Beide Methoden werden von implementiert ParallelIterator
, sodass Sie sie mit Rayon verwenden können.
Für diesen Ansatz müssen Sie steuern, wie der Iterator verwendet wird. Wenn dies durch Code erfolgt, der nicht unter Ihrer Kontrolle steht - wenn Sie beispielsweise den Iterator an itertools::merge()
oder einen ähnlichen Code übergeben , benötigen Sie einen Adapter.
Iteratoradapter: scan
Der erste Versuch, bei einem Fehler anzuhalten, besteht darin, Folgendes zu verwenden 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(())
}
Dies funktioniert, aber wir erhalten keinen Hinweis darauf, dass ein Fehler aufgetreten ist. Die Iteration wird nur stillschweigend gestoppt. Es erfordert auch das Unansehnliche, map(Result::unwrap)
was den Anschein erweckt, als würde das Programm bei einem Fehler in Panik geraten, was in der Tat nicht der Fall ist, wenn wir bei einem Fehler aufhören.
Beide Probleme können durch Umschalten von take_while
auf behoben werden. Dies scan
ist ein leistungsfähigerer Kombinator, der nicht nur das Stoppen der Iteration unterstützt, sondern auch die Elemente zurückruft, die dem Rückruf gehören, sodass der Abschluss den Fehler an den Aufrufer extrahieren kann:
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(())
}
Bei Bedarf an mehreren Stellen kann der Verschluss in eine Utility-Funktion abstrahiert werden:
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
}
}
}
... in diesem Fall können wir es als .scan(&mut err, until_err)
( Spielplatz ) aufrufen .
Diese Beispiele erschöpfen den Iterator trivial for_each()
, aber man kann ihn mit willkürlichen Manipulationen, einschließlich der von Rayon, verketten par_bridge()
. Die Verwendung scan()
ist sogar möglich, um collect()
die Elemente in einen Container zu verschieben und auf die Elemente zuzugreifen, die vor dem Fehler angezeigt wurden Result<Container, Error>
. Dies ist manchmal nützlich und beim Sammeln in nicht verfügbar .
1 Wenn Sie Rayon verwenden, um Streaming-Daten parallel zu verarbeiten, müssen Sie par_bridge () verwenden:
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)
}
Auch hier kann ein gleichwertiger Effekt nicht trivial durch Sammeln in erzielt werden Result
.