Home > other >  Idiomatic Rust for Interactively Insisting on the Name of an Existing File and Opening It
Idiomatic Rust for Interactively Insisting on the Name of an Existing File and Opening It

Time:11-21

I am parsing an Evernote export file to extract some data. I thought I'd implement the parser in Rust as a way to teach myself some of the language. I want to interactively get the name of the file containing the exported Evernote data. I found many examples for opening files in Rust online, but they all panic on an error, which is not what I want to do. I want to keep asking until the user specifies a file that can be opened for reading.

I've written the code below. It seems to work fine, but I can't believe there aren't simpler, more idiomatic solutions, so I thought I'd ask here.

It also bugs me that you can't extract the human-optimized "message" component from any error generated without writing your own text extraction function, but there is an answer on stack overflow from 2018 which suggests that this is the case. If the answer is different in 2022, I'd love to know.

    // Set up a handle for stdin.
    let stdin = io::stdin();
    
    // Set up file pointer
    let mut input_file: File;
    
    // Open a user specified file name.
    let mut file_opened = false;
    while ! file_opened {
        
        let mut filename = String::new();
        print!("Enter the name of the Evernote export file you want to convert: ");
        io::stdout().flush().expect("Encountered an unexpected error: The input buffer would not flush.");
        stdin.read_line(&mut filename).expect("Error: unable to read the file name.");
        filename = filename.trim_end().to_string();
        
        let input_file_result = File::open(filename);
        if input_file_result.is_ok() {
            file_opened = true;
            input_file = input_file_result.unwrap();
        } else {
            println!("Could not open an Evernote export file with that name. The error reported was '{:?}'.", input_file_result.err().unwrap());
        }
        
    }

CodePudding user response:

I'd write that as a loop returning a value. If your function returns an io::Result you can use ? instead of the .expects and forward the error. main too can return a Result it will show the error message of the contents of Err if you early return with ?.

use std::{io::{self, Write}, fs::File};
fn main() -> io::Result<()> {
    // Set up a handle for stdin.
    let stdin = io::stdin();

    // Like @BlackBeans noted declaring outside and .clear() inside the loop is more eficcient.
    let mut filename = String::new();
    let input_file = loop {
        print!("Enter the name of the Evernote export file you want to convert: ");
        io::stdout().flush()?;
        stdin.read_line(&mut filename)?;

        match File::open(filename.trim_end()) {
            Ok(file) => break file,
            Err(e) => println!("Could not open an Evernote export file with that name. The error reported was '{e}'."),
        }
        filename.clear()
    };
    // do something with input_file
    Ok(())
}
  • Related