Borrow in ThreadLocal::get_or, lifetime issue, noob question

How can I specify lifetimes correctly if I need to borrow some configuration in ThreadLocal::get_or?

use thread_local::ThreadLocal;

fn main() {
    let builder = Builder { config: "foo".to_string(), builds: ThreadLocal::new() };
    let build = builder.build();
    assert_eq!(build.config, "foo");
}

struct Builder<'a> {
    config: String,
    builds: ThreadLocal<Build<'a>>,
}

struct Build<'a> {
    config: &'a str
}

impl <'a> Builder<'a> {
    fn build<'b: 'a>(&'b self) -> &'b Build {
        let config = &self.config;
        self.builds.get_or(|| Build { config })
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `builder` does not live long enough
 --> src/main.rs:5:17
  |
5 |     let build = builder.build();
  |                 ^^^^^^^ borrowed value does not live long enough
6 |     assert_eq!(build.config, "foo");
7 | }
  | -
  | |
  | `builder` dropped here while still borrowed
  | borrow might be used here, when `builder` is dropped and runs the destructor for type `Builder<'_>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

You should use 'a instead of 'b

I tried using 'a but that still doesn't compile, same error: ```
error[E0597]: builder does not live long enough

I'm not sure if you can do this. Why can't you store a String in the `Build?

1 Like

This cannot work. The inner borrow must be able to outlive the outer borrow. If you have &'b &'a something, then 'a must be able to outlive 'b, but when you specify 'b: 'a, you're telling the compiler the opposite. Change it to 'a: 'b. I'm not sure if that's enough to fix the problem, tho. Try it out and report the result.

Afterwards, I saw that you provided a playground link. I've tested it myself and it didn't work. However, when I took a deeper look at your code, I saw that your struct is self-referential and Rust is unable to work with self-referential structs without the use of unsafe.

1 Like

In my real use-case, it's not a String but a struct. So I could just Clone it, but I rather want to get a better understanding of why Rust denies this.

Thanks for noticing the root cause.
I've read about self-referential things before, but don't yet fully understand it.
How should I design the structs differently to prevent the need to clone?

Ultimately the reason it doesn't work is that it's self-referential. If you want to share something like that, use an Rc or Arc or something else meant for sharing values.

Otherwise just clone it.

1 Like

The underlying problem with a self-referential structure is that, if and when the structure is moved or reallocated to a different location in memory (e.g., by reallocation of a containing vec due to change in the number of elements of the vec), the internal reference is not simultaneously updated, so instantly becomes a dangling reference. That's precisely one of the common C/C++ memory errors that safe Rust is designed to disallow.

2 Likes

Good explanation!
In this case however, I am not moving the self-referential structure.
Why does the below code compile, but not the example with ThreadLocal?

fn main() {
    let mut b = Builder { config: "foo".to_string(), builds: Vec::new() };
    b.builds.push(Build { config: &b.config });
    assert_eq!("foo", b.builds[0].config);
    b.builds.push(Build { config: &b.config });
    assert_eq!("foo", b.builds[1].config);
}

struct Builder<'a> {
    config: String,
    builds: Vec<Build<'a>>,
}

struct Build<'a> {
    config: &'a str
}

In this case you're accessing the fields directly, and the compiler is able to treat the fields as if they were separate variables. The setup you have right there works because the config field is borrowed, thus the b variable is also borrowed. This means the compiler will complain if you move b.

I think you'll find that using the struct you have there will be a major pain, because moving it, calling &mut self methods on it, and returning from the function you created it in without destroying b first are all impossible because of the borrow.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.