3
\$\begingroup\$

I'm new to Rust, coming from C++, and so to increase my knowledge of Rust I made a small program to manage an array. You can add, insert, delete, print and more an array or its elements.

I don't really care much about performance at this point, but more on readability, good practices and so on.

So, what could I have done better?

use std::io;

fn get_element() -> i64 {
    println!("Enter the element: ");

    let mut element = String::new();
    io::stdin().read_line(&mut element).expect("Failed to read line!");

    match element.trim().parse() {
        Ok(num) => num,
        Err(_) => {
            println!("Invalid number! Try again.");
            get_element()
        }
    }
}

fn get_index(length: usize) -> usize {
    println!("Enter the index: ");

    let mut index = String::new();
    io::stdin().read_line(&mut index).expect("Failed to read line!");

    match index.trim().parse() {
        Ok(index) if index < length => index,
        Ok(index) => {
            println!("Invalid index! {} is not in range [0, {})! Try again.", index, length);
            get_index(length)
        },
        Err(_) => {
            println!("Invalid number! Try again.");
            get_index(length)
        }
    }
}

fn main() {
    println!("Welcome to the Vector application!");

    let mut vec: Vec<i64> = Vec::new();

    loop {
        println!("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-");
        println!("What do you want to do?");
        println!("1. Clear the vector");
        println!("2. Add element");
        println!("3. Insert element at pos");
        println!("4. Delete an element at index");
        println!("5. Print an element");
        println!("6. Print vector");
        println!("*. Exit");

        let mut input = String::new();
        io::stdin().read_line(&mut input).expect("Failed to read line");
        let input: u8 = match input.trim().parse() {
            Ok(num) if num < 7 => num,
            Ok(num) => {
                println!("{} is not in the range [0, 6]!", num);
                continue;
            }
            Err(_) => {
                break;
            }
        };

        let length = vec.len();

        if input == 1 {
            vec.clear();
        }
        else if input == 2 {
            vec.push(get_element());
        }
        else if input == 3 {
            vec.insert(get_index(length), get_element());
        }
        else if input == 4 {
            vec.remove(get_index(length));
        }
        else if input == 5 {
            println!("The element is: {}", match vec.get(get_index(vec.len())) {
                Some(element) => element,
                None => {
                    println!("Invalid index!");
                    continue;
                }
            });
        }
        else if input == 6 {
            for i in &vec {
                println!("{} ", i.to_string());
            }
        }

    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
  1. Become familiar with rustfmt. For example, else goes on the same line as the previous closing brace:

    -        }
    -        else if input == 2 {
    +        } else if input == 2 {
    
  2. Rust doesn't do tail call optimization, so unbounded recursive calls are better replaced with iteration.

  3. I might extract a function that does the reading from standard input, trimming, and parsing.

  4. No need for a type on the vec; inference will kick in.

  5. You can use match with values like numbers. This can be used to replace the large if-else chain at the end of main.

  6. Move the command out-of-range check/warning from the "parse" match to the match of the command value. This groups the error text closer to the place that will change.

  7. I wouldn't embed the match inside the println!; it's not obvious enough. Instead, put the print inside the match.

  8. Can then remove the continue because that's the normal flow

  9. Don't convert the number to a string to print it; numbers implement Display.

  10. You check the index twice; once when getting the value, once when actually indexing. Remove one or the other.

use std::io;

fn parsed_line<T>() -> Result<T, T::Err>
    where T: std::str::FromStr
{
    let mut element = String::new();
    io::stdin().read_line(&mut element).expect("Failed to read line!");
    element.trim().parse()
}

fn get_element() -> i64 {
    loop {
        println!("Enter the element: ");

        match parsed_line() {
            Ok(num) => return num,
            Err(_) => println!("Invalid number! Try again."),
        }
    }
}

fn get_index(length: usize) -> usize {
    loop {
        println!("Enter the index: ");

        match parsed_line() {
            Ok(index) if index < length => return index,
            Ok(index) => {
                println!("Invalid index! {} is not in range [0, {})! Try again.", index, length);
            },
            Err(_) => {
                println!("Invalid number! Try again.");
            }
        }
    }
}

fn main() {
    println!("Welcome to the Vector application!");

    let mut vec = Vec::new();

    loop {
        println!("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-");
        println!("What do you want to do?");
        println!("1. Clear the vector");
        println!("2. Add element");
        println!("3. Insert element at pos");
        println!("4. Delete an element at index");
        println!("5. Print an element");
        println!("6. Print vector");
        println!("*. Exit");

        let input: u8 = match parsed_line() {
            Ok(num) => num,
            Err(_) => break,
        };

        let length = vec.len();

        match input {
            1 => vec.clear(),
            2 => vec.push(get_element()),
            3 => vec.insert(get_index(length), get_element()),
            4 => {
                vec.remove(get_index(length));
            },
            5 => println!("The element is: {}", vec[get_index(length)]),
            6 => {
                for i in &vec {
                    println!("{} ", i);
                }
            }
            other => println!("{} is not in the range [0, 6]!", other),
        };
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ Nice, thank you! :) I understand every point, except number 5. Do you refer to my huge if chain for the input? \$\endgroup\$
    – Rakete1111
    Commented Apr 4, 2017 at 18:40
  • \$\begingroup\$ @Rakete1111 ah, yes. I forgot to change that from my silly notes to myself to something that a normal human could understand; I've updated now. \$\endgroup\$
    – Shepmaster
    Commented Apr 4, 2017 at 21:12
  • \$\begingroup\$ Rust doesn't do guaranteed tail call elimination. (yet. Still hoping, we've got the become keyword reserved for just such a feature) \$\endgroup\$
    – moveaway00
    Commented May 15, 2017 at 21:13

Your Answer

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

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