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
, 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 Channel
s that can really save the day.
So... avoid global shared variable when it is possible, your brain will thank you 