Rust Book exercise 8.3.3

⚓ rust    📅 2025-05-28    👤 surdeus    👁️ 4      

surdeus

Warning

This post was published 31 days ago. The information described in this article may have changed.

Info

This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Rust Book exercise 8.3.3

After reading another user's post on this I tried to implement said exercise as well with some more advanced techniques, namely the newtype pattern and using regex to parse valid commands.

Cargo.toml

[package]
name = "departments"
version = "0.1.0"
edition = "2024"

[dependencies]
env_logger = "0.11.8"
log = "0.4.27"
regex = "1.11.1"

main.rs

extern crate core;

use std::io::stdin;

use log::{error, info};

use command::{Command, Listing};
use company::Company;

mod command;
mod company;

fn main() {
    env_logger::init();
    let mut company = Company::default();

    stdin().lines().map_while(Result::ok).for_each(|line| {
        if let Ok(command) = line.parse() {
            match command {
                Command::List(listing) => match listing {
                    Listing::Company => println!("{company}"),
                    Listing::Department(department) => {
                        if let Some(department) = company.department(&department) {
                            println!("{department}");
                        } else {
                            error!("No such department: {department}");
                        }
                    }
                },
                Command::Add {
                    employee,
                    department,
                } => {
                    company.department_mut(&department).add(&employee);
                    info!("Added {employee} to {department}");
                }
            }
        } else {
            error!("Invalid command!");
        }
    })
}

command.rs

use std::cell::LazyCell;
use std::str::FromStr;

use regex::Regex;

const LIST: LazyCell<Regex> =
    LazyCell::new(|| Regex::new(r"list(?:\s+(\w+))?").expect("Invalid regex. This is a bug."));
const ADD: LazyCell<Regex> = LazyCell::new(|| {
    Regex::new(r"add\s+(\w+)\s+to\s+(\w+)").expect("Invalid regex. This is a bug.")
});

#[derive(Debug)]
pub enum Command {
    List(Listing),
    Add {
        employee: String,
        department: String,
    },
}

impl FromStr for Command {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some(captures) = LIST.captures(s) {
            return if let Some(department) = captures.iter().flatten().nth(1) {
                Ok(Command::List(Listing::Department(
                    department.as_str().into(),
                )))
            } else {
                Ok(Command::List(Listing::Company))
            };
        }

        if let Some(captures) = ADD.captures(s) {
            let (_, [employee, department]) = captures.extract();
            return Ok(Command::Add {
                employee: employee.into(),
                department: department.into(),
            });
        }

        Err(())
    }
}

#[derive(Debug)]
pub enum Listing {
    Company,
    Department(String),
}

company.rs

use std::collections::HashMap;
use std::fmt::Display;
use std::vec::IntoIter;

use department::Department;
use department_mut::DepartmentMut;

mod department;
mod department_mut;

#[derive(Clone, Debug, Default)]
pub struct Company {
    departments: HashMap<String, Vec<String>>,
}

impl Company {
    #[must_use]
    pub fn department(&self, department: &str) -> Option<Department> {
        self.departments
            .get(department)
            .map(AsRef::as_ref)
            .map(Into::into)
    }

    #[must_use]
    pub fn department_mut(&mut self, department: &str) -> DepartmentMut<'_> {
        self.departments.entry(department.to_string()).into()
    }

    fn iter(&self) -> IntoIter<(&String, &Vec<String>)> {
        let mut departments: Vec<_> = self.departments.iter().collect();
        departments.sort_by(|(department_name_a, _), (department_name_b, _)| {
            department_name_a.cmp(department_name_b)
        });
        departments.into_iter()
    }
}

impl Display for Company {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        for (index, (department, employees)) in self.iter().enumerate() {
            write!(
                fmt,
                "Department {department}:\n{}",
                Department::from(employees.as_slice()),
            )?;

            if index + 1 < self.departments.len() {
                writeln!(fmt)?;
            }
        }

        Ok(())
    }
}

company/department.rs

use std::fmt::Display;

#[derive(Debug)]
pub struct Department<'a> {
    employees: &'a [String],
}

impl<'a> Display for Department<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut employees: Vec<_> = self.employees.to_vec();
        employees.sort();
        write!(f, "{}", employees.join(", "))
    }
}

impl<'a> From<&'a [String]> for Department<'a> {
    fn from(employees: &'a [String]) -> Self {
        Self { employees }
    }
}

company/department_mut.rs

use std::collections::hash_map::Entry;

#[derive(Debug)]
pub struct DepartmentMut<'entry> {
    entry: Entry<'entry, String, Vec<String>>,
}

impl<'entry> DepartmentMut<'entry> {
    pub fn add(self, employee: &str) {
        self.entry.or_default().push(employee.into())
    }
}

impl<'entry> From<Entry<'entry, String, Vec<String>>> for DepartmentMut<'entry> {
    fn from(inner: Entry<'entry, String, Vec<String>>) -> Self {
        Self { entry: inner }
    }
}

Any thoughts on this?

1 post - 1 participant

Read full topic

🏷️ rust_feed