0

I need to return a Vector of &str from a function. To do that I return the value in a parameter that I have passed in a reference.

Here is the initial code that I want to write:

fn read_input_file(input_file: String, path_list: &mut Vec<&str>) {
    let output_file =
        fs::read_to_string(input_file).expect("Should have been able to read the file");
    *path_list = &output_file.split('\n').collect::<Vec<&str>>();
}

I have had the following output from the compiler:

error[E0597]: `output_file` does not live long enough
   --> src\main.rs:112:18
    |
109 | fn read_input_file(input_file: String, path_list: &mut Vec<&str>) {
    |                                                            - let's call the lifetime of this reference `'1`
...
112 |     *path_list = output_file.split('\n').collect::<Vec<&str>>();
    |     ----------   ^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
    |     |
    |     assignment requires that `output_file` is borrowed for `'1`
113 | }
    | - `output_file` dropped here while still borrowed

My tentative attempt to solve it

I have tried to add lifetime to match the advice of the compiler, here the result:

fn read_input_file<'a, 'b>(input_file: String, path_list: &'a mut Vec<&str>) {
    let output_file =
        fs::read_to_string(input_file).expect("Should have been able to read the file");
    let split: &'b Split<'_, char> = &output_file.split('\n');
    *path_list = (*split.clone().collect::<Vec<&str>>()).to_vec();
}

And now I have the following output from the compiler:

error[E0597]: `output_file` does not live long enough
   --> src\main.rs:105:39
    |
102 | fn read_input_file<'a, 'b>(input_file: String, path_list: &'a mut Vec<&str>) {
    |                        -- lifetime `'b` defined here
...
105 |     let split: &'b Split<'_, char> = &output_file.split('\n');
    |                -------------------    ^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
    |                |
    |                type annotation requires that `output_file` is borrowed for `'b`
106 |     *path_list = (*split.clone().collect::<Vec<&str>>()).to_vec();
107 | }
    | - `output_file` dropped here while still borrowed

error[E0716]: temporary value dropped while borrowed
   --> src\main.rs:105:39
    |
102 | fn read_input_file<'a, 'b>(input_file: String, path_list: &'a mut Vec<&str>) {
    |                        -- lifetime `'b` defined here
...
105 |     let split: &'b Split<'_, char> = &output_file.split('\n');
    |                -------------------    ^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use
    |                |
    |                type annotation requires that borrow lasts for `'b`
106 |     *path_list = (*split.clone().collect::<Vec<&str>>()).to_vec();
107 | }
    | - temporary value is freed at the end of this statement

How can I handle this issue correctly?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jerome
  • 15
  • 4

2 Answers2

4

@7stud correctly points out that your problem is trying to create a dangling pointer which is forbidden in Rust (for good reason). To overcome this, you'll have to turn the &str which are borrowed from the local variable output_file into owned Strings:

fn read_input_file(input_file: String, path_list: &mut Vec<String>) {
    //                                                 ^^^^^^^^^^^
    //                     the signature has to change accordingly
    let output_file =
        fs::read_to_string(input_file).expect("Should have been able to read the file");

    *path_list = output_file
        .split('\n')
        .map(|borrowed_str| borrowed_str.to_string())
    //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //    turn the `&str`s borrowed from `output_file` into `String`s with `to_string()`
        .collect::<Vec<String>>();
}

As an side, using mutable references as out pointers like this is quite unusual and in most cases just adds unnecessary overhead. It's typically better to just return values.

Alternatively, to avoid all of those extra allocations, you can actually borrow from the contents of a file, but you'll have to pass them into the function from a longer-lived location. That might look like this

fn read_input<'file>(file_contents: &'file str, path_list: &mut Vec<&'file str>) {
    //        ^^^^^                  ^^^^^                           ^^^^^
    // note the lifetime annotations; they tell us that as long as the reference to
    // `file_contents` is valid, so are the references we return
    *path_list = file_contents
        .split('\n')
        .collect::<Vec<&str>>();
}

fn main() {
    let file_name = "test.txt";

    // `file` now outlives the `read_input` function call, so it's fine to return references
    // to it.
    let file = fs::read_to_string(file_name)
        .expect("Should have been able to read the file");

    // this is why out pointers are usually not used in Rust:
    // `let path_list = read_input(&file);` would just be simpler
    let mut path_list = Vec::new();

    read_input(&file, &mut path_list);
}
isaactfa
  • 5,461
  • 1
  • 10
  • 24
0

You are doing something similar to the following code:

fn main() {

    let x: &u32;

    {
        let y = 10;
        x = &y;
    }

    println!("{}", x);
}

Here's what the compiler has to say:

/rust_programs/my_prog2% cargo run
   Compiling my_prog2 v0.1.0 (/Users/7stud/rust_programs/my_prog2)
error[E0597]: `y` does not live long enough
  --> src/main.rs:22:13
   |
21 |         let y = 10;
   |             - binding `y` declared here
22 |         x = &y;
   |             ^^ borrowed value does not live long enough
23 |     }
   |     - `y` dropped here while still borrowed
24 |
25 |     println!("{}", x);
   |                    - borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `my_prog2` due to previous error

The rust compiler will not compile that code because x becomes a dangling pointer when the inner scope ends and y is dropped.

Your attempted solution did nothing to the lifetime of the output_file variable, which owns its value. The output_file variable is still going to get dropped at the end of the function, therefore any references to output_file that are still alive after the function ends will be dangling pointers--and the compiler won't let that happen.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
7stud
  • 46,922
  • 14
  • 101
  • 127