web-dev-qa-db-fra.com

Pourquoi essayer! () Et? ne pas compiler lorsqu'il est utilisé dans une fonction qui ne renvoie pas Option ou Result?

Pourquoi ce code ne compile-t-il pas?

use std::{fs, path::Path};

fn main() {
    let dir = Path::new("../FileSystem");

    if !dir.is_dir() {
        println!("Is not a directory");
        return;
    }

    for item in try!(fs::read_dir(dir)) {
        let file = match item {
            Err(e) => {
                println!("Error: {}", e);
                return;
            }
            Ok(f) => f,
        };

        println!("");
    }

    println!("Done");
}

C'est l'erreur que je reçois

error[E0308]: mismatched types
  --> src/main.rs:11:17
   |
11 |     for item in try!(fs::read_dir(dir)) {
   |                 ^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result`
   |
   = note: expected type `()`
              found type `std::result::Result<_, _>`
   = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

J'ai aussi essayé l'opérateur de point d'interrogation:

for item in fs::read_dir(dir)? {

Qui a eu une erreur différente:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
  --> src/main.rs:11:17
   |
11 |     for item in fs::read_dir(dir)? {
   |                 ^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
   |
   = help: the trait `std::ops::Try` is not implemented for `()`
   = note: required by `std::ops::Try::from_error`

Les versions précédentes de Rust présentaient une erreur similaire à propos de std::ops::Carrier

Devrais-je éviter try!() et ?? Quel est le meilleur moyen de gérer les erreurs? Surtout je le fais comme ça:

match error_prone {
    Err(e) => {
        println!("Error: {}", e);
        return;
    },
    Ok(f) => f,
};

Mais si je dois utiliser cela dans une boucle for, c'est un désordre complet

for i in match error_prone {
    // match code
} {
    // loop code
}
37
Mathieu David

try! est une macro qui renvoie Errs automatiquement; ? est une syntaxe qui fait essentiellement la même chose, mais fonctionne avec tout type implémentant le Try trait. 

À partir de Rust 1.22.0 , Option implémente Try, afin qu'il puisse être utilisé avec ?. Avant cela, ? ne pouvait être utilisé que dans des fonctions renvoyant une Result. try! continue à fonctionner uniquement avec Results.

À partir de Rust 1.26.0 , main est autorisé à renvoyer une valeur qui implémente Termination. Avant cela, il ne renvoie aucune valeur.

À partir de rouille 1.26.0

Votre code d'origine fonctionne si vous marquez main comme retournant une Result et que vous renvoyez ensuite Ok(()) dans tous les "succès"

use std::{fs, io, path::Path};

fn main() -> Result<(), io::Error> {
    let dir = Path::new("../FileSystem");

    if !dir.is_dir() {
        println!("Is not a directory");
        return Ok(());
    }

    for item in fs::read_dir(dir)? {
        let file = match item {
            Err(e) => {
                println!("Error: {}", e);
                return Ok(());
            }
            Ok(f) => f,
        };

        println!("");
    }

    println!("Done");
    Ok(())
}

Avant ça

Voici comment vous pourriez transformer votre code pour utiliser ?:

use std::{error::Error, fs, path::Path};

fn print_dir_contents() -> Result<String, Box<Error>> {
    let dir = Path::new("../FileSystem");

    if !dir.is_dir() {
        return Err(Box::from("Is not a directory!"));
    }

    for entry in fs::read_dir(dir)? {
        let path = entry?.path();
        let file_name = path.file_name().unwrap();
        println!("{}", file_name.to_string_lossy());
    }

    Ok("Done".into())
}

fn main() {
    match print_dir_contents() {
        Ok(s) => println!("{}", s),
        Err(e) => println!("Error: {}", e.to_string()),
    }
}

Vous risquez de ne pas vous attendre à beaucoup de gestion d'erreur ici - les autres langues ne l'exigent généralement pas! Mais ils existent dans d'autres langues - Rust vous le fait savoir. Voici les erreurs:

entry?

Des erreurs d'entrée-sortie peuvent survenir pendant l'itération.

path.file_name().unwrap()

Tous les chemins n'ont pas de nom de fichier. Nous pouvons unwrap ceci car read_dir ne nous donnera pas de chemin sans nom de fichier.

file_name.to_string_lossy()

Vous pouvez également to_str et générer une erreur, mais il est préférable de le faire. Cette erreur existe car tous les noms de fichiers ne sont pas Unicode valide.

try! et ? jettent les erreurs dans la valeur de retour, en les convertissant en Box::Error. En fait, il est plus raisonnable de renvoyer une erreur fusionnée de toutes les erreurs possibles. Heureusement, io::Error est le bon type:

use std::io;

// ...

fn print_dir_contents() -> Result<String, io::Error> {
    // ...

    if !dir.is_dir() {
        return Err(io::Error::new(io::ErrorKind::Other, "Is not a directory!"));
    }

    // ...
}

Franchement, cependant, cette vérification est déjà dans fs::read_dir, vous pouvez donc supprimer le if !dis.is_dir au total:

use std::{fs, io, path::Path};

fn print_dir_contents() -> Result<String, io::Error> {
    let dir = Path::new("../FileSystem");

    for entry in fs::read_dir(dir)? {
        let path = entry?.path();
        let file_name = path.file_name().unwrap();
        println!("{}", file_name.to_string_lossy());
    }

    Ok("Done".into())
}

fn main() {
    match print_dir_contents() {
        Ok(s) => println!("{}", s),
        Err(e) => println!("Error: {}", e.to_string()),
    }
}
41
Veedrac

Le ques_in_main RFC a été fusionné récemment. Une fois que complete , la syntaxe de la question sera parfaitement compilée et fonctionnera comme prévu, à condition que les appels try!() soient remplacés par l'opérateur ?.

5
Jan Nils Ferner

À partir de Rust 1.26, Rust prend en charge une valeur renvoyée par main (), ainsi que l'utilisation de l'opérateur de contrôle d'erreur ? (ou la macro try!() de manière équivalente) dans main() lorsque main() est défini pour renvoyer un Result:

extern crate failure;
use failure::Error;
use std::fs::File;

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

fn main() -> Result<()> {
    let mut _file = File::open("foo.txt")?; // does not exist; returns error
    println!("the file is open!");
    Ok(())
}

Ce qui précède compile et renvoie une erreur de fichier introuvable (en supposant que foo.txt n'existe pas dans le chemin local).

Exemple de terrain de jeu rouillé

1
U007D

La réponse de Veedrac m'a aussi aidé, bien que la question du PO soit légèrement différente. En lisant la documentation de Rust, j'ai vu cet extrait:

use std::fs::File;
use std::io::prelude::*;

let mut file = File::open("foo.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
assert_eq!(contents, "Hello, world!");

Bien que, dans le Rust Book, ils soulignent la centralité de la fonction principale, si vous l'exécutez à l'intérieur, vous obtiendrez une erreur similaire. Si vous intégrez le code dans une fonction traitant les erreurs, l'extrait de code susmentionné fonctionne:

use std::error::Error;
use std::io::prelude::*;
use std::fs::File;

fn print_file_content() -> Result<String, Box<Error>> {
    let mut f = File::open("foo.txt")?;
    let mut contents = String::new();

    f.read_to_string(&mut contents)?;

    println!("The content: {:?}", contents);

    Ok("Done".into())
}

fn main() {
    match print_file_content() {
        Ok(s) => println!("{}", s),
        Err(e) => println!("Error: {}", e.to_string()),
    }
}

P.S. J'apprends la rouille pour que ces extraits ne soient pas conçus pour être un bon code de rouille :)

0
ROLO