2
\$\begingroup\$

How could I use enums in the following block of code to simplify it? The block is a program that has a command interpreter that returns different data based on user input. Is there a way I could easily apply enums and a match statement to the logic here?

use std::io;

fn main() {
    let version = String::from("0.0.1");
    let mut input = String::new();

    shell(&mut input, &version);
}

fn shell(input: &mut String, version: &String) {
    input.clear();
    match io::stdin().read_line(input) {
        Ok(b) => {
            if &input.trim() == &"ver" || &input.trim() == &"what are you" {
                println!("C ute R ust A mateur B eginner Shell ver: {}", version);
                shell(input, version);
            } else {
                println!("Command '{}' Not Recognized", input);
                shell(input, &version);
            }
        }
        Err(err) => panic!("incorrect"),
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
  1. Use rustfmt. I know the code you've presented is formatted correctly now, but that's because I reformatted it for you over on Stack Overflow (I wouldn't have touched it here).

  2. Read and address the compiler's warning messages!

    warning: unused variable: `b`
      --> src/main.rs:13:12
       |
    13 |         Ok(b) => {
       |            ^ help: consider using `_b` instead
       |
       = note: #[warn(unused_variables)] on by default
    
    warning: unused variable: `err`
      --> src/main.rs:22:13
       |
    22 |         Err(err) => panic!("incorrect"),
       |             ^^^ help: consider using `_err` instead
    
    warning: function cannot return without recursing
      --> src/main.rs:10:1
       |
    10 | fn shell(input: &mut String, version: &String) {
       | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
    ...
    16 |                 shell(input, version);
       |                 --------------------- recursive call site
    ...
    19 |                 shell(input, &version);
       |                 ---------------------- recursive call site
       |
       = note: #[warn(unconditional_recursion)] on by default
       = help: a `loop` may express intention better if this is on purpose
    
        Finished dev [unoptimized + debuginfo] target(s) in 2.16s
         Running `target/debug/playground`
    
  3. Don't have infinite recursion; that's almost always a bad idea and you will run out of stack space. Write an infinite loop instead.

  4. There's no reason to put input outside of the function, as it's only ever used inside it.

  5. Use clippy. It will point out things like:

    warning: writing `&String` instead of `&str` involves a new object where a slice will do.
     --> src/main.rs:9:19
      |
    9 | fn shell(version: &String) {
      |                   ^^^^^^^ help: change this to: `&str`
      |
      = note: #[warn(clippy::ptr_arg)] on by default
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg
    
    warning: needlessly taken reference of both operands
      --> src/main.rs:15:16
       |
    15 |             if &input.trim() == &"ver" || &input.trim() == &"what are you" {
       |                ^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: #[warn(clippy::op_ref)] on by default
       = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
    help: use the values directly
       |
    15 |             if input.trim() == "ver" || &input.trim() == &"what are you" {
       |                ^^^^^^^^^^^^    ^^^^^
    

    See also:

  6. Use Result::expect instead of doing the match yourself. This has the beenfit of printing out the underlying error message as well.

  7. Store the result of trim to avoid doing the work multiple times.

  8. Consider using a match on the trimmed string to decide what to work on.

use std::io;

fn main() {
    let version = "0.0.1";

    shell(version);
}

fn shell(version: &str) {
    let mut input = String::new();
    loop {
        input.clear();
        io::stdin().read_line(&mut input).expect("incorrect");
        let input = input.trim();

        match input {
            "ver" | "what are you" => {
                println!("C ute R ust A mateur B eginner Shell ver: {}", version)
            }
            _ => println!("Command '{}' Not Recognized", input),
        }
    }
}

Now we can consider how best to use enums here. An enum is good for a type that can be one of many values. You have that structure in your "command" decisions. You could create an enum from those strings:

use std::io;

fn main() {
    let version = "0.0.1";

    shell(version);
}

fn shell(version: &str) {
    let mut input = String::new();
    loop {
        input.clear();
        io::stdin().read_line(&mut input).expect("incorrect");
        let command = input.trim().parse::<Command>();

        use Command::*;
        match command {
            Ok(Version) | Ok(WhatAreYou) => {
                println!("C ute R ust A mateur B eginner Shell ver: {}", version)
            }
            Err(_) => println!("Command '{}' Not Recognized", input),
        }
    }
}

#[derive(Debug)]
enum Command {
    Version,
    WhatAreYou,
}

impl std::str::FromStr for Command {
    type Err = ();

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        use Command::*;
        Ok(match input {
            "ver" => Version,
            "what are you" => WhatAreYou,
            _ => return Err(()),
        })
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ All of your notes are extremely helpful. I think it's really interesting how you added the FromStr trait to the Command enum for parsing the input string into a string. I've used your notes to modify the project I'm currently working on which is a blackjack game in rust. Thank you so much. Here's the blackjack game just in case you'd like to look over that too: play.rust-lang.org/… \$\endgroup\$ Commented Mar 1, 2019 at 16:41

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.