HashMap, borrowed value does not live long enough

In the below code, I started getting an error by adding this line:

gdp_sales.insert(data[1], data[2].parse::<f64>().unwrap());

The full code is:

use std::fs::File;
use std::io::{Write, BufReader, BufRead};
use std::io;
use std::collections::HashMap;


fn main() {
    let x = run();
    println!("{:#?}", x);
}


fn run() -> HashMap<&str, f64> {
    let path = "lines.txt";

    let mut output = File::create(path)?;
    write!(output, "year,GDP,passengars
2011,6.2,26.3
2012,6.5,26.65
2013,5.48,25.03
2014,6.54,26.01
2015,7.18,27.9
2016,7.93,30.47")?;

    let input = File::open(path)?;
    let buffered = BufReader::new(input);

    let mut gdp_sales: HashMap<&str, f64> = HashMap::new();
    for line in buffered.lines().skip(1) {
        let record = line?;
        let data: Vec<&str> = record.split(",").collect();
        gdp_sales.insert(data[1], data[2].parse::<f64>().unwrap());
        println!("{}", data[0].parse::<f64>().unwrap());
    }

    gdp_sales
}

And the error I got is:

error[E0597]: `record` does not live long enough
  --> src/main.rs:31:31
   |
31 |         let data: Vec<&str> = record.split(",").collect();
   |                               ^^^^^^ borrowed value does not live long enough
32 |         gdp_sales.insert(data[1], data[2].parse::<f64>().unwrap());
   |         --------- borrow used here, in later iteration of loop
33 |         println!("{}", data[0].parse::<f64>().unwrap());
34 |     }
   |     - `record` dropped here while still borrowed

BufReader::lines(self) creates an iterator that yields Result<String>, therefore because it consumes self and returns owned strings, it is not possible for data to live. Let me annotate your code a bit to demonstrate:

let input = File::open(path)?;
let buffered = BufReader::new(input);

let mut gdp_sales: HashMap<&str, f64> = HashMap::new(); //Lives for 'b
for line in buffered.lines().skip(1) { //Line is owned, we'll give it a lifetime 'a
    let record = line?;
    let data: Vec<&'a str> = record.split(",").collect();
    gdp_sales.insert(data[1], data[2].parse::<f64>().unwrap());
    println!("{}", data[0].parse::<f64>().unwrap());
} //'a ends here and `line` is now dropped, and it's a `String`, not a reference
//Error, your 'a data is now in 'b hashmap!

Oh, and as mentioned by @steveklabnik

is shorthand for

fn run() -> HashMap<&'static str, f64>

but only because there is a lack of other references in the signature:

fn run(something: &()) -> HashMap<&str, f64>

would be elided to this:

fn run<'a>(something: &'a ()) -> HashMap<&'a str, f64>
2 Likes

This signature is short for

fn run() -> HashMap<&'static str, f64> {

Which means that it can only have static strings as keys. Your keys aren't static, they're fetched from the records. You want

fn run() -> HashMap<String, f64> {

instead. Does that make sense? (You'll need to adjust more code for this to work)

1 Like

Thanks @OptimisticPeach and @steveklabnik for the explanations, now the below worked with me:

fn main() {
    let x = run();
    println!("{:#?}", x);
}


fn run() -> HashMap<String, f64> {
    let path = "lines.txt";

    let mut output = File::create(path).unwrap();
    write!(output, "year,GDP,passengars
2011,6.2,26.3
2012,6.5,26.65
2013,5.48,25.03
2014,6.54,26.01
2015,7.18,27.9
2016,7.93,30.47").unwrap();

    let input = File::open(path).unwrap();
    let buffered = BufReader::new(input);

    let mut gdp_sales: HashMap<String, f64> = HashMap::new();
    for line in buffered.lines().skip(1) {
        let record = line.unwrap();
        let data: Vec<&str> = record.split(",").collect();
        gdp_sales.insert(data[1].to_string(), data[2].parse::<f64>().unwrap());
        println!("{}", data[0].parse::<f64>().unwrap());
    }

    gdp_sales
}
1 Like

Is there a way to define a dummy item when I call the run()

Depends on what you want to achieve.

A naïve approach would be this:

fn run(_: &()) -> HashMap<&str, f64>

But that would be impossible to work with, as () has no ownership of something that can be turned into a &'_ str

That means "all &str that I return are contained in something", but since () doesn't contain strings, there are no strings that you could possibly return this way.

1 Like

I tired this:

fn main() {
    let s: &str = "";
    let x: HashMap<&str, f64, RandomState> = run(s);
    println!("{:#?}", x);
}

fn run<'a>(_: &'a str) -> HashMap<&'a str, f64> {
   // ...
    let mut gdp_sales: HashMap<&'a str, f64> = HashMap::new();
    for line in buffered.lines().skip(1) {
        let record = line.unwrap();
        let data: Vec<&str> = record.split(",").collect();
        gdp_sales.insert(data[1], data[2].parse::<f64>().unwrap());
        println!("{}", data[0].parse::<f64>().unwrap());
    }

    gdp_sales
}

But still getting the same error, moving to String solved it as mentioned here

Please realize that this still wouldn't work, as I explained in the first half of my previous post. Piece by piece, the data will be dropped at the end of every iteration, because line is dropped, and therefore its contents are as well.

1 Like