# Your questions
1. `HashMap<String, Vec<String>>` is fine. Since this object exists in `main` at the highest level, it must store owned values instead of references to its content, so `String` and `Vec` must be used instead of `&str`.

2. For a not yet checked error case see below, under "Missing indexing check".

# Remove `slice_vec_to_string_vec`
First of all, its name is misleading as it silently discards the first element of the input vec. It can be replaced in `command_parser` by changing this line: 
```rust
let args: Vec<&str> = c.split(' ').collect(); 
```
to:
```rust
let mut args: Vec<String> = c.split(' ').map(String::from).collect(); 
```
This directly converts all `&str` parts to `String`s efficiently.
Removing the first element afterwards is simple: 
```rust
args.remove(0);
```
`slice_vec_to_string_vec` can now be removed.

# Store an empty vec instead of `None`
`Command.args` is an `Option<Vec<String>>` right now, but it could just be a `Vec<String>` which is empty to represent `None`.
The `match cmd.args {` usages will then become `if !cmd.args.is_empty() {`.

# Prefer taking `&str` over `String` as argument type
A `String` will be automatically coerced into a `&str` cheaply, while creating a `String` from a `&str` is comparatively expensive and must be done manually.
`list_all_employees`, `add_employee`, `del_employee` can all be modified to accept a `&str`, such that calling them changes from:
```rust
del_employee(table, String::from(&cmd.args[0][..]), String::from(&cmd.args[2][..]));
```
to: 
```rust
del_employee(table, &cmd.args[0], &cmd.args[2]);
```

# Print errors with `eprintln!` instead of `println!`
`println!` only prints the message to the standard output stream, while `eprintln!` prints the message to the standard error stream.

# Use longer variable names
Spell `emp` and `dep` out as `employee` and `department`. Also, you are not using pointers, as `d_ptr`, `e_ptr`, `ptr` and your error messages suggest, but rather references. Actual pointers in Rust (`*const Type` and `*mut Type`) are rarely used since they may point to invalid data and dereferencing them is unsafe.

# `let-else` in main loop
The following destructuring assignment:
```rust
let cmd: String = match user_prompt() {
    Ok(val) => val,
    _ => {
        eprintln!("Bad user input. Please try again...");
        continue;
    }
};
```
can be expressed more concisely using let-else syntax:
```rust
let Ok(cmd) = user_prompt() else {
    eprintln!("Bad user input. Please try again...");
    continue;
};
```
The `else` branch must always diverge (meaning `return`, `break`, `continue`, panicking or ending the program otherwise) for this syntax to be usable.

# Final code
```rust
// valid commands:
// ADD name TO department, DEL name FROM department
// LIST department, LIST, QUIT
use std::{collections::HashMap, io, io::Write};

#[derive(Debug)]
struct Command {
    c_type: CommandType,
    args: Vec<String>,
}

#[derive(Debug)]
enum CommandType {
    Add,
    List,
    Del,
    Quit,
}

impl CommandType {
    fn new(val: &str) -> Option<CommandType> {
        match val {
            "ADD" => Some(CommandType::Add),
            "DEL" => Some(CommandType::Del),
            "LIST" => Some(CommandType::List),
            "QUIT" => Some(CommandType::Quit),
            _ => None,
        }
    }
}

fn main() {
    let mut employer_table: HashMap<String, Vec<String>> = HashMap::new();

    employer_table.insert(
        String::from("ENGINEERING"),
        vec![
            String::from("Lewis"),
            String::from("Ellen"),
            String::from("Alan"),
        ],
    );

    employer_table.insert(
        String::from("SALES"),
        vec![
            String::from("Jane"),
            String::from("Robert"),
            String::from("Penny"),
        ],
    );

    loop {
        let Ok(input) = user_prompt() else {
            eprintln!("Bad user input. Please try again...");
            continue;
        };

        let Some(cmd) = command_parser(input) else {
            eprintln!("Invalid command... Please type ADD, DEL, LIST or QUIT.");
            continue;
        };

        // QUIT is the only command that returns false
        if !command_executor(cmd, &mut employer_table) {
            break;
        }
    }
}

fn user_prompt() -> io::Result<String> {
    let mut buf = String::new();
    let stdin = io::stdin();

    // This is a little prompt to indicate that a terminal row is typable...
    // Copied this guide on SO to get this prompt working:
    // https://stackoverflow.com/a/41387232
    print!("> ");
    io::stdout().flush().unwrap();
    stdin.read_line(&mut buf)?;

    Ok(String::from(buf.trim())) // Remove trailing CRLF, convert str to String
}

// valid commands:
// ADD name TO department, DEL name FROM department
// LIST department, LIST, QUIT
fn command_parser(c: String) -> Option<Command> {
    let mut args: Vec<String> = c.split(' ').map(String::from).collect(); // Convert string to array of words
    let arg0 = args[0].to_uppercase();

    let c_type = CommandType::new(&arg0)?;
    args.remove(0);

    if let Some(last) = args.last_mut() {
        *last = last.to_uppercase();
    }
    Some(Command { c_type, args })
}

fn command_executor(cmd: Command, table: &mut HashMap<String, Vec<String>>) -> bool {
    match cmd.c_type {
        CommandType::Add => {
            if !cmd.args.is_empty() {
                add_employee(table, &cmd.args[0], &cmd.args[2])
            } else {
                eprintln!("Invalid arguments...")
            }
            true
        }
        CommandType::List => {
            if !cmd.args.is_empty() {
                list_all_employees(table, Some(&cmd.args[0]))
            } else {
                list_all_employees(table, None)
            }
            true
        }
        CommandType::Del => {
            if !cmd.args.is_empty() {
                del_employee(table, &cmd.args[0], &cmd.args[2])
            } else {
                eprintln!("Invalid arguments...")
            }
            true
        }
        CommandType::Quit => {
            println!("Thank you for using this shitty system. Goodbye!");
            false
        }
    }
}

fn list_all_employees(table: &HashMap<String, Vec<String>>, opts: Option<&str>) {
    match opts {
        Some(opt) => match table.get(opt) {
            Some(values) => println!("\n{}: {:#?}", opt, values),
            None => eprintln!("{} is an invalid department.", opt),
        },
        None => println!("{:#?}", table),
    }
}

fn add_employee(table: &mut HashMap<String, Vec<String>>, employee: &str, department: &str) {
    match table.get_mut(department) {
        Some(department_ref) => department_ref.push(employee.to_string()),
        None => eprintln!(
            "Failed to get a reference to the department: {} using employee: {}",
            department, employee
        ),
    };
}

fn del_employee(table: &mut HashMap<String, Vec<String>>, employee: &str, department: &str) {
    match table.get_mut(department) {
        Some(department_ref) => match department_ref.iter().position(|e| *e == employee) {
            Some(employee_index) => {
                department_ref.remove(employee_index); // Has to have semicolon so that the arm doesn't return anything
            }
            None => eprintln!("Unable to remove that element..."),
        },
        None => eprintln!(
            "Failed to get a reference to department: {} using employee: {}",
            department, employee
        ),
    };
}
```

# Further possible improvements
## Missing indexing check
Entering `Add Sally` or `Del Robert` should give an explanatory error message about the missing department, but instead panics with `index out of bounds: the len is 1 but the index is 2`.

## `to` is ignored in `Add Sally to Sales`
A word between employee and department must exist, but it can be anything. Consider checking that it is actually `to`.