5
\$\begingroup\$

I'm learning Rust by learning the Rust programming language book. I'm now implementing the solution for the third challenge.

Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in a company. For example, "Add Sally to Engineering" or "Add Amir to Sales." Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.

I decided to do a simple UI to enter commands, but the implementation is very naive overall.

Rust is my first systems language. My background is in Ruby and Javascript.

use std::collections::HashMap;
use std::io::{self, Write};
use std::str::SplitWhitespace;

fn main() {
    println!("Welcome to the department interface. Please run a command:");
    help();

    let mut departments: HashMap<String, Vec<String>> = HashMap::new();

    loop {
        print_promt();
        let command = get_command();
        let mut command_iter = command.split_whitespace();

        match command_iter.next() {
            Some("add") => match process_add(&mut command_iter) {
                Ok((employee, department)) => {
                    let mut employees = departments.entry(department).or_insert(Vec::new());
                    employees.push(employee);
                }
                Err(err) => println!("Error: {}", err),
            },
            Some("help") => help(),
            Some("list") => println!("{:#?}", &departments),
            Some("exit") => break,
            Some(_) => println!("{}: Command unknown. Try `help`", command),
            None => panic!("We shouldn't be here"),
        }
    }
}

fn help() {
    println!("help: show this help.");
    println!("exit: exits this program.");
    println!("show: shows the deparment structure.");
    println!("add: adds an employee to a department");
    println!("     - usage: `add <employee name> to <department_name>`");
}

fn print_promt() {
    print!("> ");
    io::stdout().flush().ok().expect("Couldn't flush stdout!");
}

// I think this can be made better returning an Enum/Struct with a parsed command.
fn get_command() -> String {
    let mut command = String::new();
    io::stdin()
        .read_line(&mut command)
        .expect("Couldn't read command!");
    command
}


// The compiler told me to add the lifetime, but I don't understand why (I haven't reached that
// part of the book yet.)
// FIXME `SplitWhitespace` is too specific.
fn process_add<'a>(command: &mut SplitWhitespace) -> Result<(String, String), &'a str> {
    let mut employee = String::new();
    let mut department = String::new();

    // For a command "add Ronald Reagan to Old Presidents"
    // - Consume the iterator onto `employee` until you find "to""
    // - Consume the rest of the iterator onto `department`
    // Using two loops feels naive though. I don't like the duplication inside them.
    //
    // Maybe `String` can be extended and I could add a `push_str_with_separator` method that does
    // the extra check.
    //     
    //      employee.push_str_with_separator(word);
    //
    while let Some(word) = command.next() {
        if word == "to" {
            break;
        }

        if employee.len() > 0 {
            employee.push_str(" ");
        }
        employee.push_str(word)
    }

    for word in command {
        if department.len() > 0 {
            department.push_str(" ");
        }
        department.push_str(word);
    }

    if employee.len() > 0 && department.len() > 0 {
        Ok((employee, department))
    } else {
        Err("Could not parse command. Try `add <employee name> to <department name>")
    }
}
\$\endgroup\$

0

Your Answer

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

Browse other questions tagged or ask your own question.