Ich habe eine Funktion, die Folgendes zurückgibt Result
:
fn find(id: &Id) -> Result<Item, ItemError> {
// ...
}
Dann ein anderer, der es so benutzt:
let parent_items: Vec<Item> = parent_ids.iter()
.map(|id| find(id).unwrap())
.collect();
Wie gehe ich mit dem Fehlerfall in einer der map
Iterationen um?
Ich weiß, ich könnte verwenden flat_map
und in diesem Fall würden die Fehlerergebnisse ignoriert :
let parent_items: Vec<Item> = parent_ids.iter()
.flat_map(|id| find(id).into_iter())
.collect();
Result
Der Iterator hat je nach Erfolgsstatus entweder 0 oder 1 Elemente und flat_map
filtert ihn heraus, wenn er 0 ist.
Ich möchte jedoch keine Fehler ignorieren , sondern den gesamten Codeblock stoppen und einen neuen Fehler zurückgeben (basierend auf dem Fehler, der in der Karte aufgetreten ist, oder den vorhandenen Fehler einfach weiterleiten).
Wie gehe ich in Rust am besten damit um?
quelle
collect
, die für den Rückgabetyp, der implementiert werden muss, polymorph istFromIterator
. Ich weiß nicht, was Sie unter "kann es breiter angewendet werden" verstehen. Rust unterstützt polymorphe Rückgabetypen ... Also ja? ( Weitere Beispiele für Polymorphismus vom Rückgabetyp finden Sie unterRng
undDefault
Merkmale.)from_iter
wird in dercollect
Methode aufgerufen .collect()
muss der Iterator endlich sein, richtig? Wenn ja, wie würde ein ähnlicher, aber unendlicher Iterator behandelt?map()
s tun ? Wenn der erste amap()
zurückgibtResult
, muss der folgendemap()
auch a akzeptieren,Result
was ärgerlich sein kann. Gibt es eine Möglichkeit, dasselbe von der Mitte dermap()
Kette aus zu erreichen?.map(...).collect<Result<Vec<_>, _>>()?.into_iter().map(...)
Natürlich nicht nur zu tun .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öchtenmax()
oder eine andere konsumierende Methode. In anderen Situationen ist es fast unmöglich, als wenn die aufwändige Methode ist in einer anderen Kiste, wieitertools
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 implementiertParallelIterator
, 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. Diesscan
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, verkettenpar_bridge()
. Die Verwendungscan()
ist sogar möglich, umcollect()
die Elemente in einen Container zu verschieben und auf die Elemente zuzugreifen, die vor dem Fehler angezeigt wurdenResult<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
.quelle
sum()
dieOk
Elemente [...] möchten - das ist bereits in der Standardbibliothek implementiert und verwendet dieselbe Technik wie dieprocess_results
Methode in itertools.process_results()
, danke. Der Vorteil ist, dass keine separate Fehlervariable erforderlich ist. Die Nachteile sind, dass es nur als Funktion der obersten Ebene verfügbar ist, die Sie aufruft (mögliches Problem, wenn mehrere Dinge parallel durchlaufen werden), und dass eine externe Kiste erforderlich ist. Der Code in dieser Antwort ist relativ kurz, funktioniert mit stdlib und nimmt an der Verkettung von Iteratoren teil.Sie können hierfür die
std::result::fold
Funktion verwenden. Es hört auf zu iterieren, nachdem es auf das erste gestoßen istErr
.Ein Beispielprogramm, das ich gerade geschrieben habe:
fn main() { println!("{}", go([1, 2, 3])); println!("{}", go([1, -2, 3])); } fn go(v: &[int]) -> Result<Vec<int>, String> { std::result::fold( v.iter().map(|&n| is_positive(n)), vec![], |mut v, e| { v.push(e); v }) } fn is_positive(n: int) -> Result<int, String> { if n > 0 { Ok(n) } else { Err(format!("{} is not positive!", n)) } }
Ausgabe:
Ok([1, 2, 3]) Err(-2 is not positive!)
Demo
quelle