Rust Book Hashtables Exercise: General Review + Option Use

Hi all, I've just started learning rust, and I've hit Chapter 8.3 of the Rust Book, where there's an exercise for the reader:

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 have a working solution, but I would love to get feedback if this is a reasonable/idiomatic approach, especially if my use of cloned on the Option<&str> to reduce the amount of indirection is reasonable.

Here's my solution:

use std::io::{stdout, stdin, Write};
use std::collections::HashMap;

fn main() {
    println!("Welcome to the employee database.");
    println!("Commands are:
        'ADD PERSON TO DEPARTMENT'
        'LIST [DEPARTMENT]
        'EXIT'");

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

    loop {
        print!("> ");
        stdout().flush().expect("Failed to flush stdout");

        let mut cmd = String::new();
        stdin().read_line(&mut cmd).expect("Failed to read stdin");

        let cmd_parts: Vec<&str> = cmd.split_whitespace().collect();
        
        if let Some(task) = cmd_parts.first() {
            let task = task.to_lowercase();

            match task.as_str() {
                "add" => add_person(cmd_parts.get(1).cloned(), cmd_parts.get(3).cloned(), &mut departments),
                "list" => list_people(cmd_parts.get(1).cloned(), &mut departments),
                "exit" => break,
                _ => println!("Invalid command")
            }
        }

    }
}

fn add_person(person: Option<&str>, department_name: Option<&str>, departments: &mut HashMap<String, Vec<String>>) {
    if let (Some(person), Some(department_name)) = (person, department_name) {
        println!("Adding {person} to {department_name}");

        let department = departments.entry(String::from(department_name)).or_insert(Vec::new());
        department.push(String::from(person));
    }
    else {
        println!("Missing name or department.")
    }

}

fn list_people(department: Option<&str>, departments: &mut HashMap<String, Vec<String>>){
    // if a department is specified, list people in department, sorted alphabetically
    if let Some(department_name) = department {
        if let Some(people) = departments.get_mut(department_name) {
            people.sort();
            println!("People in department: {department_name}");
            println!("-------");

            people.iter().for_each(|x| println!("{x}"));
        }
        else {
            println!("Invalid department.");
        }
    }

    // if no department is given, list all people, sorted alphabetically
    else {
        let mut people = departments.values()
            .into_iter()
            .flatten()
            .collect::<Vec<&String>>();

        people.sort();

        println!("All people in company:");
        println!("-------");
        people.iter().for_each(|x| println!("{x}"));
    }

}

Thank you for any help or feedback!

Looks really good to me, I have very few comments!

You might want to validate the "TO" keyword.

I like the way you sorted String refs when sorting all people, since this avoids cloning the Strings.

For sorting within a department, I thought about the use of a BTreeSet to maintain sorted order, with the added benefit of checking for duplicate names when inserting. But checking for duplicates means you would have to normalize the names, and there was no specification for that. So I think deferring the sorting until you need it is right. And it would also depend on how frequently the names would be listed.

It would be slightly better to use copied() since cloning a reference is unusual. There is no difference in behavior, it just looks unusual.

Instead of

                department.push(String::from(person));

it is a little more idiomatic to use

                department.push(person.to_string());

or

                department.push(person.into());

These are all very minor comments.

1 Like

That's a really helpful! And I'm glad to hear my first draft was good.

Thank you so much for your help.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.