Andrew
Andrew

Reputation: 2173

Why do I get a "Borrowed value does not live long enough" error for a value I don't need outside of a function?

Why does this happen in this certain scenario? I'm new to Rust, read the 2nd edition book, but.. well, yeah, here I am. :)

fn main() {
    Xyz::new(&"whatever=123");
}

pub struct Xyz<'a> {
    x: &'a str
}

impl<'a> Xyz<'a> {
    pub fn new(query: &str) -> Result<Xyz<'a>, &'a str> {
        let mut qkey: String = String::new();
        let mut qval: String = String::new();

        let mut is_key = true;
        for (i, c) in query.chars().enumerate().skip(1) {
            if c == '=' {
                is_key = false;
            } else if c == '&' {
                is_key = true;
            } else if is_key {
                qkey.push(c);
            } else {
                qval.push(c);
            }

            if c == '&' || i == query.len() - 1 {
                match qkey.as_ref() {
                    "whatever" => {
                        let _whatever = Xyz::some_func(&mut qval)?;
                    }
                    _ => (),
                }

                qkey.clear();
                qval.clear();
            }
        }
        Ok(Xyz { 
            x: &""
        })
    }

    fn some_func(_val: &mut String) -> Result<bool, &str> {
        Ok(true)
    }
}

playground

Error:

error[E0597]: `qval` does not live long enough
  --> src/main.rs:29:61
   |
29 |                         let _whatever = Xyz::some_func(&mut qval)?;
   |                                                             ^^^^ borrowed value does not live long enough
...
41 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 9:1...
  --> src/main.rs:9:1
   |
9  | impl<'a> Xyz<'a> {
   | ^^^^^^^^^^^^^^^^

I don't understand why the actual error happens. I know the note should help me understand the problem, but it doesn't.

I don't need qval outside of this function, so why do I have to still keep it? Is there a conceptual error I made?

Upvotes: 0

Views: 2608

Answers (1)

loganfsmyth
loganfsmyth

Reputation: 161637

A trimmed down reproduction of your issue is

pub struct Xyz<'a> {
    x: &'a str
}

impl<'a> Xyz<'a> {
    pub fn new(_query: &str) -> Result<Xyz<'a>, &'a str> {
        let qval: String = String::new();

        Err(&qval)
    }
}

with the error

error[E0597]: `qval` does not live long enough
  --> src/main.rs:13:18
   |
13 |             Err(&qval)
   |                  ^^^^ borrowed value does not live long enough
14 |         }
   |         - borrowed value only lives until here

To understand this, I'd recommend taking a step back and thinking about what exactly your function is doing. You create a String, and then take a reference to it, and then return that reference.

This is would be a classic case of use-after-free. From the standpoint of the language, the string no longer exists, so returning a reference to it does not make sense.

Returning &str could make sense, but only if you can guarantee that the &str will only reference data that is still in scope, even after the function has returned. For instance, it would be valid to do Err(&query) (or any subsection of query) if you adjust the function to be fn new(query: &'a str) ->, since then it is it is known that the return value lives as long as the input string.

It is hard to tell from your example if that would be acceptable for usecase.

The simplest would indeed be to return a String (or Result<Xyz<'a>, String>> in your case, possibly with a String in the field for Xyz too). You could also consider returning something like a Cow which is sometimes a subsection of query or a static string, and sometimes a String.

As a side-note, I'll also add that Xyz::new(&"whatever=123"); does not need the & since it is already a reference. You can do

Xyz::new("whatever=123");

just fine. Same for &"".

My confusion was that - in the not stripped down version - both function (new and some_func returned a global &'static str as Err, so I thought the compiler would know that the string would always exist.

This may be the core of the misunderstanding. When you declare

fn some_func(_val: &mut String) -> Result<bool, &str> {

the &str is not 'static. Rust's lifetime elision logic means that this declaration will assume that the reference in the function argument and the reference in the return value have the same lifetime, meaning that what you've declared is

fn some_func<'b>(_val: &'b mut String) -> Result<bool, &'b str> {

which triggers the error you are seeing. If you want it to be static, you'll need to say so, as

fn some_func(_val: &mut String) -> Result<bool, &'static str> {

which would avoid the error, and require that some_func always returns a 'static string.

Upvotes: 4

Related Questions