3
\$\begingroup\$

Q: Am I approaching writing idiomatic Rust code correctly?

Hi CodeReview, I'm a beginner Rustacean coming from Python going through Rust books (The Rust Book, Rust in Action). A lot of the beginner code has unwrap everywhere and I'd like to write more idiomatic Rust.

I wrote 2 versions of a simple CLI program that reads a file and prints it to stdout, one with .unwrap and one without.

Both version execute successfully and do the same thing, only differing in the error-handling.

I tried to expand out all cases of .unwrap based on things in the official docs about using .unwrap[1][2][3].

Protoyping

extern crate clap;

use std::{
    prelude,
    io::{BufRead, BufReader},
    fs::File,
};
use clap::{App, Arg};

fn main() {
    let matches = App::new("File reader")
        .about("Read a file line by line")
        .help("The help text")
        .version("0.1")
        .arg(Arg::with_name("input")
            .value_name("INPUT")
            .takes_value(true)
            .help("Any text file to be read"))
        .get_matches();

    // Get input argument, ensure it is valid
    let input = matches.value_of("input").unwrap();

    // Try and open the file
    let f = File::open(input).unwrap();

    // Load the file into BufReader
    let reader = BufReader::new(f);

    // Read the lines of the file
    for line_ in reader.lines() {
        let line = line_.unwrap();
        println!("{}", line);
        }
    }

Improving robustness

use std::{
    error::Error,
    // prelude::*,
    io::{BufRead, BufReader},
    fs::File,
};
use clap::{App, Arg};

fn main() -> Result<(), Box<dyn Error>> {
    let matches = App::new("File reader")
        .about("Read a file line by line")
        .help("The help text")
        .version("0.1")
        .arg(Arg::with_name("input")
            .value_name("INPUT")
            .takes_value(true)
            .help("Any text file to be read"))
        .get_matches();

    // Get input argument, ensure it is valid
    let input = matches.value_of("input");

    let input = match input {
        Some(file) => file,
        None => matches.usage(),
    };

    // Try and open the file
    let f = File::open(input)?;

    // Load the file into BufReader
    let reader = BufReader::new(f);

    // Read the lines of the file
    // "if let" reads "if 'let' destructures line_ into Ok(line) value, evaluate {}"
    for line_ in reader.lines() {
        if let Ok(line) = line_ {
            println!("{}", line);
        };
    }
    Ok(())
}

Am I approaching making my code more idiomatic correctly?

Edit 2: My second take at making it more readable/idiomatic (fn main only)

fn main() -> Result<(), Box<dyn Error>> {
    let matches = App::new("File reader")
        .about("Read a file line by line")
        .help("The help text")
        .version("0.1")
        .arg(Arg::with_name("input")
            .value_name("INPUT")
            .takes_value(true)
            .help("Any text file to be read"))
        .get_matches();

    // Get input argument, ensure it is valid
    let input = match matches.value_of("input") {
        Some(input) => input,
        None        => matches.usage(),
    };

    // Try and open the file
    if let Ok(f) = File::open(input){
        // If valid, load into BufReader then read the file line by line
        let reader = BufReader::new(f);
        for line_ in reader.lines() {
            if let Ok(line) = line_ {
                println!("{}", line);
            };
        };
    } else {
        // If file is not valid, print the CLI usage
        println!("{}", matches.usage())
    };
    Ok(())
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.