1
\$\begingroup\$

Whenever I try a new language I always try to write a speed-distance-time calculator as well as possible. I did this with C++ a while ago now. This is a similar question but this time in Rust.

The program calculates speed, distance or time from the other two variables in the equation and the idea is to try to do this without a load of nested if statements and repetition. I try to make as elegant a solution as possible but the program needs to show the working for the equation.

I want to know how I could adjust the program to:

  • Fit in with good (Rust) practices
  • Shorten the program as much as possible
  • Catch all errors
  • Add support for units and conversions later on as well as other common equations
  • Make better use of Rust
    use std::io;


// Simple input function to simplify calc functions

fn get_param(s: &str) -> f64 {
    println!("{}", s); // Print input instructions

    loop { // loop until a float is entered
        println!("Enter a floating point number!");
        let mut input = String::new();

        io::stdin().read_line(&mut input)
                .expect("failed to read line");

        let input: f64 = match input.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        return input;
    }
}

fn calc_speed() -> f64 {
    let distance: f64   = get_param("Enter distance:");
    let time: f64       = get_param("Enter time:");
    println!("Speed = distance / time");
    println!("Speed = {} / {}", distance, time);
        distance / time
}

fn calc_time() -> f64 {
    let speed: f64      = get_param("Enter speed:");
    let distance: f64   = get_param("Enter distance:");
    println!("Time = distance / speed");
    println!("Time = {} / {}", distance, speed);
        distance / speed
}

fn calc_distance() -> f64 {
    let speed: f64      = get_param("Enter speed:");
    let time: f64       = get_param("Enter time:");
    println!("Distance = speed * time");
    println!("Distance = {} * {}", speed, time);
        speed * time
}


fn main() {   
    let mut calc_input = String::new();

    println!("Hello, would you like to calculate speed, distance or time?");

    io::stdin().read_line(&mut calc_input)
            .expect("failed to read line");

    let answer: f64;

    match calc_input.trim() {
        "speed" | "Speed"       => {
            answer = calc_speed();
            println!("Speed = {}", answer);
        }
        "distance" | "Distance" => {
            answer = calc_distance();
            println!("Distance = {}", answer);
        }
        "time" | "Time"         => {
            answer = calc_time();
            println!("Time = {}", answer);
        }
        _                       => {
            println!("Enter speed, distance or time!")
        }
    }

}

I am new to Rust and know only a little about the language, coming primarily from C++. I know if I was writing C++ I would probably write a sdt class and maybe a struct for working.

\$\endgroup\$
  • \$\begingroup\$ write a sdt class — I'm unfamiliar with the term "sdt class"; what does it mean? Searching didn't seem to return anything obvious. \$\endgroup\$ – Shepmaster May 18 '16 at 22:19
  • \$\begingroup\$ Oh, it's the initials of "speed, distance, time", isn't it? \$\endgroup\$ – Shepmaster May 18 '16 at 22:21
  • \$\begingroup\$ @Shepmaster oh I meant a class for speed distance and time. By sdt I meant speed distance time - wasn't clear at all, sorry \$\endgroup\$ – exitcode May 18 '16 at 22:21
1
\$\begingroup\$
  1. You have extra indentation on the use statement and in your implicit returns.

  2. I don't like the detail printed between the prompt and the entry of the value:

    Enter distance:
    Enter a floating point number!
    

    I'd reorder these two lines, or combine them.

  3. There's no need for the match in the prompt to evaluate to a value, it can directly return from the function.

  4. This change allows you to switch to an if let statement because you'd only care about one variant.

  5. If you keep the loop structure to read strings, you could consider keeping one allocated String to store into. You need to be careful to reset the string between loops, but it's a bit more efficient.

  6. There's no need to specify the type on variables in the calc_* functions. Type is defined by the call to get_param.

  7. Could extract some duplication for the prompt "Enter ...:" and move it to the prompt function.

  8. Actually, "prompt" is a better verb than "get"; "get" is almost always a weak verb.

  9. Don't declare a variable with a wider scope than needed - declare answer inside the match arms in main.

  10. Actually, there's no reason to have a variable for answer at all, inline the call into the println!.

  11. Actually, why split the printing inside and outside of the calc_* functions? Might as well do it all in one place.

  12. What about supporting various cases of input like "SpEED"? Could match against a lowercased version of the string.


use std::io;

fn prompt_for_value(value_name: &str) -> f64 {
    println!("Enter {}:", value_name);

    loop {
        println!("Enter a floating point number!");

        let mut input = String::new();
        io::stdin().read_line(&mut input).expect("failed to read line");

        if let Ok(num) = input.trim().parse() {
            return num;
        }
    }
}

fn calc_speed() {
    let distance = prompt_for_value("distance");
    let time     = prompt_for_value("time");
    println!("Speed = distance / time");
    println!("Speed = {} / {}", distance, time);
    println!("Speed = {}", distance / time);
}

fn calc_time() {
    let speed    = prompt_for_value("speed");
    let distance = prompt_for_value("distance");
    println!("Time = distance / speed");
    println!("Time = {} / {}", distance, speed);
    println!("Time = {}", distance / speed);
}

fn calc_distance() {
    let speed = prompt_for_value("speed");
    let time  = prompt_for_value("time");
    println!("Distance = speed * time");
    println!("Distance = {} * {}", speed, time);
    println!("Distance = {}", speed * time);
}

fn main() {
    println!("Hello, would you like to calculate speed, distance or time?");

    let mut calc_input = String::new();
    io::stdin().read_line(&mut calc_input).expect("failed to read line");

    match calc_input.to_lowercase().trim() {
        "speed"    => calc_speed(),
        "distance" => calc_distance(),
        "time"     => calc_time(),
        _          => println!("Enter speed, distance or time!"),
    }
}

Here's an alternate version of prompt_for_value that I might have written, but it's a bit different.

use std::io::BufRead;

fn prompt_for_value(value_name: &str) -> f64 {
    let stdin = io::stdin();

    println!("Enter {} (floating-point):", value_name);

    for line in stdin.lock().lines() {
        let line = line.expect("failed to read line");

        if let Ok(num) = line.trim().parse() {
            return num;
        }
    }

    panic!("Ran out of input");
}
| improve this answer | |
\$\endgroup\$

Your Answer

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

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