Catty: A mini cat clone in Rust As part of my journey in learning the Rust programming language, I decided to make a miniature cat clone (catty) in it. The following is my code, which depends on clap for argument parsing (see below). It currently only supports concatenating 1 file with possibly numbered lines (-n/--number). I tried to be as close as possible to the actual cat program for this:

#[macro_use] extern crate clap;

use std::{io, error, env, fs::read_to_string, path::PathBuf, process};

fn main() {
    process::exit(
        if let Err(err) = cli(env::args().collect::&lt;Vec&lt;_&gt;&gt;()) {
            // CLI parsing errors
            if let Some(clap_err) = err.downcast_ref::&lt;clap::Error&gt;() {
                eprint!("{}", clap_err);
            } else {
                eprintln!("{}", err);
            }
            1
        } else {
            0
        }
    );
}

fn cli(cli_args: Vec&lt;String&gt;) -&gt; Result&lt;(), Box&lt;error::Error&gt;&gt; {
    let matches = clap::App::new("catty")
        .version(crate_version!())
        .about("A minimal clone of the linux utility cat.")
        .arg(clap::Arg::with_name("FILE")
            .help("The file to concatenate to standard output")
            .required(true))
        .arg(clap::Arg::with_name("number")
            .short("n")
            .long("number")
            .help("Numbers all output lines"))
        .get_matches_from_safe(cli_args)?;

    let file_contents = get_file_contents(matches.value_of("FILE").unwrap())?;
    let file_contents: Vec&lt;&amp;str&gt; = file_contents.split("\n").collect();

    let number_lines = matches.is_present("number");

    for (i, line) in file_contents.iter().enumerate() {
        let formatted_line = if number_lines {
            format!("{:&gt;6}  {}", i + 1, line)
        } else {
            line.to_string()
        };

        if i == file_contents.len() - 1 &amp;&amp; line.len() &gt; 0 {
            print!("{}", formatted_line);
        } else if !(i == file_contents.len() - 1 &amp;&amp; line.len() == 0) {
            println!("{}", formatted_line);
        }
    }

    Ok(())
}

fn get_file_contents(passed_argument: &amp;str) -&gt; Result&lt;String, Box&lt;error::Error&gt;&gt; {
    let mut resolved_path = PathBuf::from(passed_argument);

    if !resolved_path.exists() || !resolved_path.is_file() {
        resolved_path = PathBuf::from(env::current_dir()?);
        resolved_path.push(passed_argument);

        if !resolved_path.exists() || !resolved_path.is_file() {
            return Err(io::Error::new(io::ErrorKind::NotFound, "The passed file is either not a file or does not exist!").into());
        }
    }

    Ok(read_to_string(resolved_path)?)
}


My Cargo.toml is as follows:

[package]
name = "catty"
version = "0.1.0"
authors = ["My Name &lt;my@email.com&gt;"]

[dependencies]

[dependencies.clap]
version = "2.32"
default-features = false
features = ["suggestions"]


Here is what I want to know from this code review:


Is my code idiomatic rust (i.e good error handling, not overly verbose, etc)?
Is my code performant or can it be improved in some way?
4: use std::{env, error, fs::read_to_string, io, path::PathBuf, process};

Just a personal taste, but I would do

use std::{env, error, io, process};
use std::fs::read_to_string;
use std::path::PathBuf;


Yes, nested includes are nice and easy, but hard to extend. It's up to you.



35: let file_contents: Vec&lt;&amp;str&gt; = file_contents.split('\n').collect();

I would suggest using lines instead.

Also, you can omit &amp;str or change the line completly.

Either 

let file_contents: Vec&lt;_&gt; = file_contents.lines().collect();


or

let file_contents = file_contents.lines().collect::&lt;Vec&lt;_&gt;&gt;();




46/48: line.len() &gt; 0 / line.len() == 0

Replace that by !line.is_empty() and line.is_empty().



60: resolved_path = PathBuf::from(env::current_dir()?);

Remove PathBuf::from completly, because current_dir is already a PathBuf