Borrowed value does not live long enough, reading file


#1

Trying to complete the advent of code (I know it’s a little bit late) I have the following problem when I try to read the input file.

I created a new module with some utility methods that I can use to solve every excercise. This file contains just functions to help read the file content and organizing the output:

use std::fs;

pub fn read_file(filename: &str) -> String {
    fs::read_to_string(filename)
        .expect("Something went wrong reading the file.")
}

pub fn get_file_content_lines(filename: &str) -> Vec<&str> {
    let content = read_file(filename);

    content.lines().collect()
}

pub fn read_and_convert_to_int(file_content: Vec<&str>) -> Vec<i32> {
    let mut numbers: Vec<i32> = vec![];

    for line in file_content {
        numbers.push(line.parse::<i32>().unwrap());
    }

    numbers
}

With the following error:

error[E0597]: `content` does not live long enough
     --> src/utility.rs:13:5
   |
13 |     content.lines().collect()
   |     ^^^^^^^ borrowed value does not live long enough
14 | }
   | - borrowed value only lives until here

#2

Well, content is a String, and lines returns references into that string, that you’re trying to collect into a Vec and return them. But at the end of the function, the local variable content is dropped, so the references become dangling.

If you’re only interested in a quick fix, you can return a Vec<String> from get_file_content, and them make the last line this:

content.lines().map(String::from).collect()

#3

okay, it works thank you.

Since I’m learning Rust, what would it be the ‘slower’ fix? I could I fix it by returning a Vec<&str>?


#4

To have a Vec<&str>, you must have a String which live at least as long as this Vec. That means, it must be created before the function containing collect() call is called.


#5

Okay, I’ve done it.

Thank you.


#6

Yes, but it needs more than that. Did you read the book yet, the chapter on ownership and references? If so, the following outlines what you could do: Find another owner for content, maybe just the main function. Then pass a reference to it into get_file_content_lines (which would then probably be called just get_lines or something), which then could return a Vec<&str> since the references then would not have to refer to a local variable. This has the advantage of not having to copy each line, at the expense of a more elaborate setup. But it’s a good learning step for sure to do this, I suggest you try and come back if you have trouble with it.


#7

I just created a variable containing the String before the call to read_file:

pub fn get_file_content_lines(filename: &str) -> Vec<&str> {
    let mut content = String::new();
    content = read_file(filename);

    content.lines().collect()
}

This solution works, and in this case I can keep all together.


#8

Could you explain a bit more? I wasn’t able to get this code to work (playground), since content now still is too short-lived.


#9

Yesterday I installed rust on this pc and I don’t know why it installed and older version of rust. I just updated it and… it doesn’t works anymore.


#10

That’s basically the same as before and should never have worked, for the same reasons.


#11

Just checked on rust 1.26 (the first version to have fs::read_to_string, AFAIK) - the same error. Anyway, we’ve already told where is the problem - feel free to ask if something is not clear yet.