Reputation: 565
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
Upvotes: 1
Views: 183
Reputation: 86
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 (&str
s 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 String
s 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"]}
Upvotes: 1