Borrowed value does not live long enough for returned static variable

I'm new to Rust, could somebody help to fix this? Thanks!

I want to return a global static Vec, but it can't build. The code is put in Playground, a short code paste here:

static mut g_prev_locators: Option<&mut Locators> = None;
fn prev_locators() -> &'static mut Locators {
	unsafe {
		match g_prev_locators {
			Some(ref mut s) => &mut **s,
			None => {
				g_prev_locators = Some(&mut Locators::new());
				g_prev_locators.unwrap()
			}
		}
	}
}

The build error:

   |                 g_prev_locators = Some(&mut Locators::new());
   |                                             ^^^^^^^^^^^^^^^ - temporary value only lives until here
   |                                             |
   |                                             temporary value does not live long enough

I am not sure about what you would like to try, so my reply could be useless.

First of all, it looks like a very nice footgun :grin:, and I will explain you why. Assuming we make your code compile, g_prev_locators is shared across threads and it is mutable. Therefore, you can easily take the location from prev_locators, then take the value out from g_prev_locators and deng you have a dangling pointer (I am assuming you wanted to store Locators inside g_prev_locators, not its reference, otherwise you are not storing it anywhere).

Now, what I think you wanted to do is initialize a common Locators that can be accessed from many functions. First of all, you have to decide if you want to be thread local or not, because this makes a difference. I will show you both approaches.

Thread local

The basic read-only approach is not so hard to do, take a look:

#[derive(Debug)]
pub struct Locators(pub Vec<(u64, u64)>);

impl Locators {
	pub fn new() -> Locators {
		Locators(vec![])
	}
}

thread_local! {
    static LOCATORS: Locators = Locators::new();
}

fn main() {
    LOCATORS.with(|locators| println!("loc={:?}", locators));
}

As you can see, I used the macro thread_local! to create the thread local LOCATORS variable, which has been already initialized when we access it from the pritnln!. Note that it is necessary to use .with to access it, and that is necessary to access the variable safely.

However, we cannot modify it with this code. If you check the documentation, you will notice that there is no .with_mut, so we need interior mutability. You can use a Cell or a RefCell, I will show you a simple example with the latter.

use std::cell::RefCell;

#[derive(Debug)]
pub struct Locators(pub Vec<(u64, u64)>);

impl Locators {
	pub fn new() -> Locators {
		Locators(vec![])
	}
}

thread_local! {
    static LOCATORS: RefCell<Locators> = RefCell::new(Locators::new());
}

fn main() {
    LOCATORS.with(|locators| {
        println!("loc={:?}", *locators);
        locators.borrow_mut().0.push((0, 1));
        println!("loc={:?}", *locators);
    });
}

Now the RefCell is checking at runtime that there is only one mutable reference at a time, and everything is safe.

Global scope

Sometimes it is necessary to use shared variables across threads. This is a bit more complicated, and there is a valid reason for this (cough cough data races cough).
You already noticed that you cannot declare a static mut variable unless you mark it as unsafe, but don't do that unless you know what are you doing (read: don't do it). Let's try some approaches, the compiler will help us to understand what is right and what is wrong.

static LOCATORS: RefCell<Locators> = RefCell::new(Locators::new());

With this, we get two different errors.

error[E0015]: calls in statics are limited to tuple structs and tuple variants
  --> src/main.rs:12:51
   |
12 | static LOCATORS: RefCell<Locators> = RefCell::new(Locators::new());
   |                                                   ^^^^^^^^^^^^^^^
   |
note: a limited form of compile-time function evaluation is available on a nightly compiler via `const fn`
  --> src/main.rs:12:51
   |
12 | static LOCATORS: RefCell<Locators> = RefCell::new(Locators::new());
   |                                                   ^^^^^^^^^^^^^^^

error[E0277]: `std::cell::RefCell<Locators>` cannot be shared between threads safely
  --> src/main.rs:12:1
   |
12 | static LOCATORS: RefCell<Locators> = RefCell::new(Locators::new());
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::cell::RefCell<Locators>` cannot be shared between threads safely
   |
   = help: the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<Locators>`
   = note: shared static variables must have a type that implements `Sync`

The first one is telling us that we cannot initialize the static variable in this way. The second one tells us that we cannot share a RefCell across threads.

To solve the first problem, we can use the crate lazy_static, which helps us avoiding some boilerplate code.

#[macro_use]
extern crate lazy_static;
// ...
lazy_static! {
    static ref LOCATORS: RefCell<Locators> = RefCell::new(Locators::new());
}

The second problem remains. We need some sort of cross-threads synchronization, for example a sync::Mutex.

use std::sync::Mutex;
// ...
lazy_static! {
    static ref LOCATORS: Mutex<Locators> = Mutex::new(Locators::new());
}

Now we can access the internal values once we have a lock.

use std::borrow::BorrowMut;
// ...
fn main() {
    let mut locators = LOCATORS.lock().unwrap();
    println!("loc={:?}", *locators);
    locators.borrow_mut().0.push((0, 1));
    println!("loc={:?}", *locators);
}

The lock guard must be mutable to use the trait BorrowMut (which must be imported), that's the reason of having let mut locators.

Here the final result.

#[macro_use]
extern crate lazy_static; // 1.0.2

use std::{sync::Mutex, borrow::BorrowMut};

#[derive(Debug)]
pub struct Locators(pub Vec<(u64, u64)>);

impl Locators {
	pub fn new() -> Locators {
		Locators(vec![])
	}
}

lazy_static! {
    static ref LOCATORS: Mutex<Locators> = Mutex::new(Locators::new());
}

fn main() {
    let mut locators = LOCATORS.lock().unwrap();
    println!("loc={:?}", *locators);
    locators.borrow_mut().0.push((0, 1));
    println!("loc={:?}", *locators);
}

Final remarks

As you can see, working with global variables is cumbersome, and it is generally a good practice to avoid them when it is possible. There are lots of ways to obtain similar results which less code and better performance, i.e. using Rc<T>, Rc<RefCell<T>> or, when working in multithread, Arc<T>, Arc<Mutex<T>> or Arc<RwLock<T>>. Furthermore, there are higher-level synchronization capabilities in the stdlib like Channels that can really save the day.
So... avoid global shared variable when it is possible, your brain will thank you :wink:

1 Like