Working my way through the second edition of the Rust Programming Language and did the exercise at the end of ch 8:
Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in the 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.
My questions about my code are as follows:
- Do I handle errors correctly?
- Is the code more or less idiomatic Rust?
- Is there a way to make the code more efficient?
lib.rs:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
}
}
pub mod add_employee;
pub mod department_list;
pub mod company_list;
pub mod selection;
pub mod select_activity;
main.rs:
extern crate company_hr;
use std::collections::HashMap;
use company_hr::selection::*;
use company_hr::add_employee::*;
use company_hr::select_activity::*;
use company_hr::department_list::*;
use company_hr::company_list::*;
fn main() {
let mut data = HashMap::new();
loop {
let activity = select_activity();
match activity {
1 => { let data = add_employee(&mut data); continue },
2 => { println!("List employees from which department?\n");
let dept = selection_text();
department_list(&data, &dept); continue },
3 => { company_list(&data); continue},
4 => break,
_ => continue
}
}
}
selection.rs:
use std::io;
pub fn selection() -> Result<u32, ::std::num::ParseIntError> {
let mut selection = String::new();
io::stdin().read_line(&mut selection).expect("failed to read line");
selection.trim().parse::<u32>()
}
pub fn selection_text() -> String {
let mut selection = String::new();
io::stdin().read_line(&mut selection).expect("failed to read line");
selection.trim().to_string().to_uppercase()
}
select_activity.rs:
use ::selection::*;
pub fn select_activity() -> u32 {
println!("Enter '1' to add an employee to a department.\nEnter '2'
to list employees within a department.\nEnter '3' to list
all company employees by department.\nEnter '4' to
exit.");
check_activity()
}
pub fn check_activity() -> u32 {
let selection = selection();
match selection {
Ok(i) => i,
Err(_) => select_again()
}
}
pub fn select_again() -> u32 {
println!("Please enter a number 1 through 4.");
select_activity()
}
add_employee.rs:
use std::collections::HashMap;
use ::selection::*;
pub fn add_employee(mut data: &mut HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>> {
loop {
println!("Please enter the name of the employee you would like
to manage.");
let employee = selection_text();
let employee = employee.trim();
println!("Please enter the name of the department you would
like to add the employee to.");
let department = selection_text();
let department = department.trim();
data.entry(department.to_string())
.or_insert(vec![])
.push(employee.to_string());
match check_continue() {
1 => continue,
2 => break,
_ => match try_again() {
1 => continue,
2 => break,
_ => {println!("Too many errors. Assuming you wish to
exit current function."); break}
}
}
}
data.to_owned()
}
pub fn select_continue() -> Result<u32, ::std::num::ParseIntError> {
println!("Enter '1' to add more employees, '2' to finish adding
employees.");
selection()
}
pub fn check_continue() -> u32 {
let selection = select_continue();
match selection {
Ok(i) => i,
Err(_) => try_again()
}
}
pub fn try_again() -> u32 {
println!("Please enter '1' or '2'.");
check_continue()
}
department_list.rs:
use std::collections::HashMap;
use std::collections::hash_map::Entry;
pub fn department_list(data: &HashMap<String, Vec<String>>, dept: &str) {
match data.clone().entry(dept.to_string()) {
Entry::Occupied(mut entry) => {
let mut vals: Vec<_> = entry.get_mut().iter().collect();
vals.sort();
println!("\nEmployees in department {}:", dept);
for x in vals {
println!("{}\n", x)
}
},
Entry::Vacant(entry) => {println!("The department {} does not
exist or does not have any employees
yet. You may add departments through
the 'add an employee to a department'
activity. The current departments
are:", dept);
let mut keys: Vec<_> = data.keys().collect();
keys.sort();
for x in keys {
println!("{}", x)
}
}
}
}
company_list.rs:
use std::collections::HashMap;
use ::department_list::*;
pub fn company_list(data: &HashMap<String, Vec<String>>) {
let depts = data.keys();
println!("Company employees listed by department:");
for dept in depts {
department_list(data, dept)
}
}
expect()
, and your parse statements seem very likely to crash the program. you might want to use something likeunwrap()
\$\endgroup\$expect()
calls tounwrap_or()
calls to still get the custom message if unwrap returns aNone
value. It's been a long time since I thought about this project. Thanks for reminding me about it. I might take another stab at it now that I'm a little more versed in Rust. \$\endgroup\$