1
\$\begingroup\$

I'm making a small program to benchmark Rust's performance compared to some other languages. The idea I came up with, was to take a file (with the numbers 1 to 10000000 written to it in separate lines) as a command line argument:

1
2
3
...
9999999
10000000

read the file, and then multiple every line in that file by two and write those back to the same file, so the end result would look like this:

2
4
6
...
19999998
20000000

My code works, and does exactly what it should do. I am especially looking for ways how I could make the code cleaner while also making it run faster. I'm currently running it with:

$ cargo run --release numbers.txt

The program:

use std::env;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;

fn main() {
    let args: Vec<String> = env::args().collect();
    let file_path = &args[1];

    let mut file = OpenOptions::new().read(true).open(file_path).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
    let vec: Vec<i32> = contents.lines()
        .map(|line| line.trim().parse::<i32>().unwrap() * 2)
        .collect();

    let mut file = OpenOptions::new().write(true).open(file_path).unwrap();
    for line in vec {
        write!(file, "{}\n", line).unwrap();
    }
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

This is my first post here, but I thought I might give this one a shot.

  • Your code could be made more user friendly by providing custom error messages.

    • When you get the filename from the command line arguments, you assume that the user will provide at least one command line argument. You could provide a helpful error message if they don't.
    • If opening a file fails for some reason (probably that the file doesn't exist at all), you could provide a custom print-out.
    • My example uses expect() as an example, but in code where user-friendliness is actually a goal, you would probably want to use eprintln!("error message here") and std::process::exit(1) to quit the program. That way, you don't get rust's extra panic messages which a user shouldn't have to understand.
  • You don't actually have to allocate a Vec to read the command line arguments, you can use the .nth method on the Args iterator.

  • You don't need the trim() call, because the lines() iterator already removes the newlines.

  • If you are looking to make the code cleaner, you could open the file for reading with File::open, and open it for writing with File::create.

  • I don't know how to significantly improve the performance of your code.

    • The biggest thing that I notice is that when you write the processed sequence of numbers to the file, you call write! on every line. This means that you talk to the OS every time you write a number. This could be made faster by using BufWriter, which would let you talk to the file only once. Alternatively, you could construct your output string while you are reading the file.
use std::env;
use std::fs::File;
use std::io::{BufReader, BufRead};
use std::io::Write;

fn main() {
    let file_path = &env::args().nth(1).expect("must provide an input file");

    let file = File::open(file_path).expect("failed to open input file");

    let doubled_contents: String = BufReader::new(file).lines()
        .map(|line| {
            let number = line.unwrap().parse::<i32>().expect("failed to parse file");
            format!("{}\n", number*2)
        })
        .collect();

    let mut output_file = File::create(file_path).expect("failed to open output file");
    write!(output_file, "{}", doubled_contents).expect("could not write output");
}

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.