1
votes

Comment agréger des itérateurs sur des erreurs lors de l'utilisation de filter_map avec une fonction de mappage faillible?

Supposons que je veuille lire tous les fichiers d'un répertoire. Je pourrais définir une fonction comme celle-ci:

use std::path::Path;

fn read_all(dir: &Path) -> Result<Vec<String>> {
    Ok(fs::read_dir(dir)?
        .filter_map(|entry| read_entry(entry).unwrap())
        .collect())
}

Et ensuite essayer de l'utiliser ...

use std::error::Error;
use std::fs;
use std::io;

type Result<T> = std::result::Result<T, Box<dyn Error>>;

fn read_entry(entry: io::Result<fs::DirEntry>) -> Result<Option<String>> {
    let entry = entry?;
    if entry.file_type()?.is_file() {
        Ok(Some(fs::read_to_string(entry.file_name())?))
    } else {
        Ok(None)
    }
}

Cela compile mais c'est juste unwrap () . collect () peut normalement agréger les itérateurs sur les erreurs, mais je ne comprends pas vraiment comment faire cela avec filter_map () . Comment puis-je résoudre ce problème?

Playground


2 commentaires

Est-ce que cela répond à votre question? Comment éviter de déballer lors de la conversion d'un vecteur d'options ou de résultats uniquement en valeurs réussies?


Non, je ne veux pas ignorer les erreurs.


3 Réponses :


4
votes

Il y a une belle implémentation de FromIterator pour Iter (qui est un trait sous-jacent pour Iterator :: collect code>), donc cela fonctionne:

fn read_all(dir: &Path) -> Result<Vec<String>> {
    fs::read_dir(dir)?
        .filter_map(|entry| read_entry(entry).transpose())
        .collect()
}


1 commentaires

Aha transpose () était le chaînon manquant! Merci!



1
votes

Au lieu de renvoyer Result> , vous avez besoin de Option> , c'est-à-dire avant:

  • Ok (Some (T)) : C'était un fichier et nous l'avons lu avec succès
  • Ok (Aucun) : Ce n'était pas un fichier mais nous n'avons eu aucune erreur pour le découvrir.
  • Err : une erreur s'est produite.

Après:

  • Certains (Ok (T)) : C'était un fichier et nous l'avons lu avec succès
  • Aucun : ce n'était pas un fichier mais nous n'avons eu aucune erreur pour le découvrir.
  • Certains (Err) : une erreur s'est produite.

Vous pouvez modifier l'implémentation d'origine, mais cela signifie que vous ne pouvez pas utiliser l'opérateur ? , ce qui est nul.

Une meilleure solution - comme l'a souligné @Kitsu est d'utiliser la fonction intégrée transpose () qui convertira un Result> en une Option > .


2 commentaires

D'accord, de plus je changerais même l'argument read_entry en entry: fs :: DirEntry , alors il peut être utilisé sans fermeture supplémentaire, c'est-à-dire .filter_map (read_entry )


Mais read_dir () retourne un itérateur sur les Result s ...?



0
votes

Juste pour terminer, la raison pour laquelle read_entry échoue est parce qu'elle utilise entry.file_name () au lieu de entry.path () .

Donc ceci:

Ok(Some(fs::read_to_string(entry.path())?))

devrait être:

Ok(Some(fs::read_to_string(entry.file_name())?))


0 commentaires