0
votes

Trying to complete the "Hash Maps" chapter of the Rust book at https://doc.rust-lang.org/book/2018-edition/ch08-03-hash-maps.html , with this code:

extern crate regex;

use std::collections::HashMap;
use std::io;
use regex::Regex;

fn get_command() -> String {
    let mut input_cmd = String::new();

    io::stdin().read_line(&mut input_cmd)
        .expect("Failed to read command");
    let input_cmd = input_cmd.trim();

    input_cmd.to_string()
}

fn main() {
    println!("Add someone by typing e.g. \"Add Sally to Engineering\", list everyone in a department by typing e.g. \"List everyone in Sales\", or list everyone by typing \"List everyone\". To quit, type \"Quit\".");

    let mut employees_by_dept: HashMap<&str, Vec<&str>> = HashMap::new();

    let add_to_dept_re = Regex::new("^Add ([A-Za-z]+) to ([A-Za-z]+)$").unwrap();
    let list_in_dept_re = Regex::new("^List everyone in ([A-Za-z]+)$").unwrap();
    let list_all_re = Regex::new("^List everyone$").unwrap();

    loop {
        let input_cmd = get_command();
        let caps = add_to_dept_re.captures(&input_cmd).unwrap();

        if add_to_dept_re.is_match(&input_cmd) {
            let dept_name = caps.get(2).unwrap().as_str();
            let employee_name = caps.get(1).unwrap().as_str();

            println!("Adding person");
            employees_by_dept.entry(&dept_name)
                .or_insert_with(Vec::new)
                .push(employee_name);
        } else if list_in_dept_re.is_match(&input_cmd) {
            println!("Listing people");
        } else if list_all_re.is_match(&input_cmd) {
            println!("Listing everyone");
        } else if input_cmd == "Quit" {
            break;
        } else {
            println!("Invalid command");
            break;
        }
    }

    println!("Bye!");
}

But I get this:

error[E0597]: `input_cmd` does not live long enough
  --> src/main.rs:28:45
   |
28 |         let caps = add_to_dept_re.captures(&input_cmd).unwrap();
   |                                             ^^^^^^^^^ borrowed value does not live long enough
...
48 |     }
   |     - `input_cmd` dropped here while still borrowed
...
51 | }
   | - borrowed value needs to live until here

Have tried .captures(&input_cmd.clone()) and various other things, but doesn't help. Any ideas?

1
Why do you think you need references as keys of your map? Neither HashMap<&str, T nor Vec<&str> are very useful/convenient types. You most likely want owned String instead.mcarton

1 Answers

4
votes

Rust memory safety rules prevents this type of approach: your HashMap value outlives the inserted items.

See embedded comments below but especially the Ownership chapter of the book.

fn main() {
    let mut employees_by_dept: HashMap<&str, Vec<&str>> = HashMap::new();

    let add_to_dept_re = Regex::new("^Add ([A-Za-z]+) to ([A-Za-z]+)$").unwrap();
    let list_in_dept_re = Regex::new("^List everyone in ([A-Za-z]+)$").unwrap();
    let list_all_re = Regex::new("^List everyone$").unwrap();

    loop {
        let input_cmd = get_command();
        let caps = add_to_dept_re.captures(&input_cmd).unwrap();// <--- input_cmd 
                                                                //is borrowed here

        // ... code for getting dept_name and employee_name references
        //     and inserting into HashMap omitted

    } // <----- The String input_cmd is dropped here (memory is freed)
      // this implies that dept_name and employee_name references 
      // points to deallocated memory

    // ... At this point you will have a live employees_by_dept HashMap
    //     that contains references to deallocated memory

    println!("Bye!");
}

Make instead the HashMap take ownership of the keys/items values:

fn main() {
    println!("Add someone by typing e.g. \"Add Sally to Engineering\", list everyone in a department by typing e.g. \"List everyone in     Sales\", or list everyone by typing \"List everyone\". To quit, type \"Quit\".");

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

    let add_to_dept_re = Regex::new("^Add ([A-Za-z]+) to ([A-Za-z]+)$").unwrap();
    let list_in_dept_re = Regex::new("^List everyone in ([A-Za-z]+)$").unwrap();
    let list_all_re = Regex::new("^List everyone$").unwrap();

    loop {
        let input_cmd = get_command();
        let caps = add_to_dept_re.captures(&input_cmd).unwrap();

        if add_to_dept_re.is_match(&input_cmd) {
            let dept_name = caps.get(2).unwrap().as_str();
            let employee_name = caps.get(1).unwrap().as_str();

            println!("Adding person");

            employees_by_dept
                .entry(dept_name.to_string())
                .or_insert_with(Vec::new)
                .push(employee_name.to_string());
        } else if list_in_dept_re.is_match(&input_cmd) {
            println!("Listing people");
        } else if list_all_re.is_match(&input_cmd) {
            println!("Listing everyone");
        } else if input_cmd == "Quit" {
            break;
        } else {
            println!("Invalid command");
            break;
        }
    }
    println!("Bye!");
}