J'ai un code comme celui-ci:
let things = vec![/* ...*/]; // e.g. Vec<String>
things
.map(|thing| {
let a = try!(do_stuff(thing));
Ok(other_stuff(a))
})
.filter(|thing_result| match *thing_result {
Err(e) => true,
Ok(a) => check(a),
})
.map(|thing_result| {
let a = try!(thing_result);
// do stuff
b
})
.collect::<Result<Vec<_>, _>>()
En termes de sémantique, je veux arrêter le traitement après la première erreur.
Le code ci-dessus fonctionne, mais il semble assez lourd. Y a-t-il une meilleure façon? J'ai cherché dans les documents quelque chose comme filter_if_ok
, Mais je n'ai rien trouvé.
Je connais collect::<Result<Vec<_>, _>>
, Et cela fonctionne très bien. J'essaie spécifiquement d'éliminer le passe-partout suivant:
match
sur thing_result
. Je pense que cela ne devrait être qu'une ligne, par exemple .filter_if_ok(|thing| check(a))
.map
, je dois inclure une instruction supplémentaire let a = try!(thing_result);
afin de faire face à la possibilité d'un Err
. Encore une fois, j'ai l'impression que cela pourrait être résumé dans .map_if_ok(|thing| ...)
.Y a-t-il une autre approche que je peux utiliser pour obtenir ce niveau de concision, ou ai-je juste besoin de l'endurcir?
Vous pouvez implémenter ces itérateurs vous-même. Voyez comment filter
et map
sont implémentés dans la bibliothèque standard.
map_ok
la mise en oeuvre:
#[derive(Clone)]
pub struct MapOkIterator<I, F> {
iter: I,
f: F,
}
impl<A, B, E, I, F> Iterator for MapOkIterator<I, F>
where
F: FnMut(A) -> B,
I: Iterator<Item = Result<A, E>>,
{
type Item = Result<B, E>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|x| x.map(&mut self.f))
}
}
pub trait MapOkTrait {
fn map_ok<F, A, B, E>(self, func: F) -> MapOkIterator<Self, F>
where
Self: Sized + Iterator<Item = Result<A, E>>,
F: FnMut(A) -> B,
{
MapOkIterator {
iter: self,
f: func,
}
}
}
impl<I, T, E> MapOkTrait for I
where
I: Sized + Iterator<Item = Result<T, E>>,
{
}
filter_ok
est presque le même:
#[derive(Clone)]
pub struct FilterOkIterator<I, P> {
iter: I,
predicate: P,
}
impl<I, P, A, E> Iterator for FilterOkIterator<I, P>
where
P: FnMut(&A) -> bool,
I: Iterator<Item = Result<A, E>>,
{
type Item = Result<A, E>;
#[inline]
fn next(&mut self) -> Option<Result<A, E>> {
for x in self.iter.by_ref() {
match x {
Ok(xx) => if (self.predicate)(&xx) {
return Some(Ok(xx));
},
Err(_) => return Some(x),
}
}
None
}
}
pub trait FilterOkTrait {
fn filter_ok<P, A, E>(self, predicate: P) -> FilterOkIterator<Self, P>
where
Self: Sized + Iterator<Item = Result<A, E>>,
P: FnMut(&A) -> bool,
{
FilterOkIterator {
iter: self,
predicate: predicate,
}
}
}
impl<I, T, E> FilterOkTrait for I
where
I: Sized + Iterator<Item = Result<T, E>>,
{
}
Votre code peut ressembler à ceci:
["1", "2", "3", "4"]
.iter()
.map(|x| x.parse::<u16>().map(|a| a + 10))
.filter_ok(|x| x % 2 == 0)
.map_ok(|x| x + 100)
.collect::<Result<Vec<_>, std::num::ParseIntError>>()
Il y a de nombreuses façons de dire cela.
Si vous voulez simplement paniquer, utilisez .map(|x| x.unwrap())
.
Si vous voulez que tous les résultats o une seule erreur, collect
dans un Result<X<T>>
:
let results: Result<Vec<i32>, _> = result_i32_iter.collect();
Si vous voulez tout sauf les erreurs, utilisez .filter_map(|x| x.ok())
ou .flat_map(|x| x)
.
Si vous voulez tout jusqu'à la première erreur, utilisez .scan((), |_, x| x.ok())
.
let results: Vec<i32> = result_i32_iter.scan((), |_, x| x.ok());
Notez que ces opérations peuvent être combinées avec des opérations antérieures dans de nombreux cas.
Puisque Rust 1.27, Iterator::try_for_each
pourrait être intéressant:
Une méthode d'itérateur qui applique une fonction faillible à chaque élément de l'itérateur, s'arrêtant à la première erreur et renvoyant cette erreur.
Cela peut également être considéré comme la forme faillible de
for_each()
ou comme la version sans état detry_fold()
.
filter_map
peut être utilisé pour réduire les cas simples de mappage puis de filtrage. Dans votre exemple, il y a une certaine logique dans le filtre, donc je ne pense pas que cela simplifie les choses. Je ne vois pas non plus de fonctions utiles dans la documentation de Result
malheureusement. Je pense que votre exemple est aussi idiomatique que possible, mais voici quelques petites améliorations:
let things = vec![...]; // e.g. Vec<String>
things.iter().map(|thing| {
// The ? operator can be used in place of try! in the nightly version of Rust
let a = do_stuff(thing)?;
Ok(other_stuff(a))
// The closure braces can be removed if the code is a single expression
}).filter(|thing_result| match *thing_result {
Err(e) => true,
Ok(a) => check(a),
}
).map(|thing_result| {
let a = thing_result?;
// do stuff
b
})
L'opérateur ?
Peut être moins lisible dans certains cas, vous pouvez donc ne pas vouloir l'utiliser.
Si vous pouvez modifier la fonction check
pour renvoyer Some(x)
au lieu de true, et None
au lieu de false, vous pouvez utiliser filter_map
:
let bar = things.iter().filter_map(|thing| {
match do_stuff(thing) {
Err(e) => Some(Err(e)),
Ok(a) => {
let x = other_stuff(a);
if check_2(x) {
Some(Ok(x))
} else {
None
}
}
}
}).map(|thing_result| {
let a = try!(thing_result);
// do stuff
b
}).collect::<Result<Vec<_>, _>>();
Vous pouvez vous débarrasser de la let a = try!(thing);
en utilisant une correspondance dans certains cas également. Cependant, l'utilisation de filter_map
Ici ne semble pas aider.