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\$ 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\$ 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\$ Commented May 15, 2017 at 21:13

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.