Search code examples
vectorrusthashmap

Modify Vec into HashMap


I have a file key/value:

key1,valueA
key1,valueB
key2,valueC
..
..

And I've to store the file in a HashMap<&str, Vec<&str>>, the above file becomes:

["key1 -> vec![valueA,valueB]]
["key2 -> vec![valueC]]
..
..

My code reads a single line from the file, search in the hashmap and if the key exists, then it adds the value to the list, otherwise it creates a new element in a hashmap with the key and vec[value]

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

    let file = File::open(file_in).unwrap();
    let reader = BufReader::new(file);

    for line in reader.lines() {
        let a =  line.unwrap().split(",").collect::<Vec<&str>>();
        // a[0] -> key
        // a[1] -> value
        match rows.get_mut(&a[0]) {
            Some(stored_row) => {
                stored_row.push(a[1]);
            }
            None => {
                rows.insert(&a[0], vec![a[1]]);
            }
        }
    }

The error is:

error[E0716]: temporary value dropped while borrowed
   |
28 |         let a =  line.unwrap().split(",").collect::<Vec<&str>>();
   |                  ^^^^^^^^^^^^^                                  - temporary value is freed at the end of this statement
   |                  |
   |                  creates a temporary which is freed while still in use
...
31 |         match rows.get_mut(&a[0]) {
   |                             - borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

Solution

  • I am also a learner of rust. I would like to give it a try.

    The direct cause of the error is that the variable a is a vector containing &str references that point to a temporary String variable generated by the line.unwrap() (that is moved from the temporary Result variable line). As mentioned above, the temporary String variable has a shorter lifetime than we need. The referred String should live as long as the (&strs in) HashMap.

    One of the interesting decisions is where we want to store the actual bytes. (1) If we would like to change our mind to use HashMap<String, Vec<String>> type, then everything is more straightforward -- we keep allocating memory and making new Strings and we are free of lifetime issues. (2) If we insist on using &str in the hashmap, then we can use an external buffer to hold the actual contents (bytes) of keys and maps and make sure the buffer lives at least at long as the hashmap. For option (2), we can:

    
    // File: data.txt
    // key1,valueA
    // key1,valueB
    // key2,valueC
    
    
    use std::collections::HashMap;
    use std::fs::File;
    use std::io::*;
    
    fn main() {
        // buffer to hold all bytes in the memory that
        // lives longer than `&str` in HashMap
        let mut buffer = String::new(); 
        // hashmap
        let mut rows: HashMap<&str, Vec<&str>> = HashMap::new();
    
        // slurp the file into a string buffer
        let file_in = "data.txt";
        let file = File::open(file_in).unwrap();
        let mut reader = BufReader::new(file);
        reader.read_to_string(&mut buffer).expect("err in reading into a string");
    
        // parse the buffer and add items to hashmap
        for line in buffer.trim().split("\n") {
            let mut n_field = 0;
            let mut k: &str = "";
            let mut v: &str = "";
            for field in line.split(",") {
                // parse key field
                if n_field == 0 {
                    k = field;
                }
                // parse value field
                else if n_field == 1 {
                    v = field;
                }
                n_field += 1;
            }
            // check the number of fields
            if n_field != 2 {
                panic!("incorrect number of fields");
            }
            // if key already in map, just push the value to corresponding
            // value vector
            if let Some(v_vec) = rows.get_mut(k){
                v_vec.push(v);
            }
            // if key is new, make a new value vector containing the new
            // value and insert the (key, value_vec) pair
            else {
                let v_vec = vec![v];
                rows.insert(k, v_vec);
            }
    
        }
    
        println!("{:?}", rows);
    }
    // output:
    // {"key1": ["valueA", "valueB"], "key2": ["valueC"]}