web-dev-qa-db-fra.com

Quelle est la façon la plus idiomatique de travailler avec un itérateur de résultats?

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:

  • Dans la fermeture du filtre, je dois utiliser match sur thing_result. Je pense que cela ne devrait être qu'une ligne, par exemple .filter_if_ok(|thing| check(a)).
  • Chaque fois que j'utilise 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?

15
Tim McLean

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>>()

aire de jeux

5
aSpex

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.

26
Veedrac

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 de try_fold().

5
arkod

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.

3
pengowen123