Borrowed value does not live long enough, trying to turn String into &str

I am trying to format two &str values (one literal one is a value of a vector).
When I format it though it returns a string, so I try to turn it back into a &str so I can use it for the mdata vector
Here's the code:

fn part2(data: Vec<&str>) {
    let mut mdata = data.clone();
    for i in 0..data.len() {
        let currentline = &mdata[i];
        let parts = currentline.split(" ").collect::<Vec<_>>();
        if parts[0] != "acc" {
            if parts[0] == "nop" {
               let somestr = format!("jmp {:?}",parts[1]).to_owned();
                let hopestr = somestr.as_str();
               mdata[i] = hopestr;
            } else {
                let some_string = format!("jmp {}",parts[1]);
                let some_str = some_string.as_str();
                mdata[i] = some_str;
            }
        let mut taken: Vec<i32> = Vec::new();
        part1(mdata.clone(),&mut taken,0i32,&mut 0i32,true);
        } else {
            continue;
        }
    }
}


This is the error.
I have absolutely no idea what to do with it. I tried to reproduce the error here:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e2cee34d88ef4320f7fc43b1fd35304c
But this worked without errors. I ran the same thing locally and it worked without errors too. I have no idea what is going on, please tell me a solution and how the solution works.
As a little context I'm trying to make code to parse a file and solve part 2 of this problem:
https://adventofcode.com/2020/day/8

I have looked at many similar issues but I couldn't find a solution.
Thank you in advance.

The reason you could not reproduce it is because you ignored where the destructor of some_string gets called. To reproduce the error, you need something like this:

fn main() {
    let randomstr = "hello world";
    let mut mdata: Vec<&str> = vec!["some", "thing"];
    {
        let parts = randomstr.split(" ").collect::<Vec<_>>();
        let some_string = format!("jmp {}", parts[1]);
        let some_str = some_string.as_str();
        mdata[0] = some_str;
    }
    println!("{:#?}", mdata);
}
error[E0597]: `some_string` does not live long enough
  --> src/main.rs:7:24
   |
7  |         let some_str = some_string.as_str();
   |                        ^^^^^^^^^^^ borrowed value does not live long enough
8  |         mdata[0] = some_str;
9  |     }
   |     - `some_string` dropped here while still borrowed
10 |     println!("{:#?}",mdata);
   |                      ----- borrow later used here

This fails because a &str can only exist while the String that owns the data is still alive, so if you try to keep it alive past where the String gets destroyed, it will fail to compile.

The solution is to use Vec<String> instead of Vec<&str>.

some_str is a local variable and thus will be freed when the block of code it is in exits, so you can't assign it to mdata which requires all of its strings to live longer than that.

Something you could do is make mdata a Vec<Cow<'_, str>> to allow it to store either references or owned values, e.g.

let mut mdata: Vec<_> = data.iter().map(|&s| Cow::Borrowed(s)).collect();

And then you set the entry in mdata to Cow::Owned if you want to store a String in it.

Trying to wrap my head around this. Rust can be very confusing coming from a Javascript background :thinking:

I'm going to see if this is going to work, I'm confident it will :wink:

Yeah, in javascript, the existence of a reference will itself keep the value alive, but in Rust it is the other way around: the reference doesn't do anything to keep the reference alive — the compiler just looks at where the value is destroyed, and where the reference exists, and throws an error if the reference lives too long.

Regarding Cow, it's going to be a lot simpler to just use String.

The problem is that somestr is assigned the String and hopestr / mdata[i] are a reference to that string. But somestr will be dropped / freed as soon as the scope / the block { ... } ends:

            if parts[0] == "nop" {
               let somestr = format!("jmp {:?}",parts[1]).to_owned();
                let hopestr = somestr.as_str();
               mdata[i] = hopestr;
               // somestr will be dropped /freed here but mdata[i] still has a reference to it!!!
            } else {

I think you need to re-organize your code. Can you store the String in mdata[i] instead of a reference?

I did know it would be much easier to use String, but I don't want to change my 60 lines of code to turn Vec<&str> to Vec<String>.
I also thought about using .map() to turn the vector into Vec temporarily and turn it back into Vec<&str> when it is done but that didn't seem like an efficient solution.

You aren't going to get it to work with Vec<&str>. A &str is fundamentally a borrowed type, and such a reference can never outlive the string it points no.

I guess it's time to use Vec<String> and refactor my code like crazy to adjust for it.

I'm guessing you were using &str because that is the type of a string literal, but those are only ok because such a string literal becomes a reference into an immutable global variable, and the reference is therefore valid forever.

To turn a &str into a String, you can use the_str.to_string().

It's sometimes best to just bite the bullet and refactor. The farther into a project you get before thinking about structure the more pain you will have in the long run.

Another way out of this is something along the lines of

let somestr = if parts[0] == "nop" {
    ...
} else {
    ...
}
mdata[i] = somestr;

I like defining my variables conditionally this way and find it tends to reduce the overall code size. You're basically making the definition of somestr conditional on parts[0] without putting inside a new scope. Where Rust has syntactic sugar, it tends to be really useful and it pays to learn these little tricks.

That would only work if they did this once, but since they're putting it into a Vec in a loop, that approach won't work.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.