How safe is SyncLazy<RwLock<T>>?

Hello, often times when I write a GUI application (which is mainly with Gtk-rs), I find myself needing an external Datastate in the global namespace, which is independent from my UI struct. In other words, I need a global mutable variable. I understand that it can cause Data Race. Recently I discovered SyncLazy and I am thinking to use this as my datastate:

static DATA_RWLCK: SyncLazy<RwLock<DataState>> = SyncLazy::new(|| {
    RwLock::new(DataState {
        balance: 23,
        values: vec![String::from("Hello"), String::from("World")],
    })
});

fn main() {
    {
        DATA_RWLCK
            .write()
            .unwrap()
            .values
            .push(String::from("Test"));
        println!("{}", DATA_RWLCK.read().unwrap().balance);
        for value in DATA_RWLCK.read().unwrap().values.iter() {
            println!("{}", value);
        }
    }
}

What are the potential dangers of such pattern? Also, how does SyncOneCell differ from a SyncLazy<RwLock<T>>?

There are many lazy-initialization crates. lazy_static is the most popular one.

You can't have a data race in Rust without broken code in unsafe blocks. If your code compiles, it's safe from data races. In this case RwLock already guarantees lack of data races.
Outer SyncLazy is just to run the constructor on demand, because Rust won't run code before main(), so it would refuse to init Strings otherwise.

You could have higher-level race conditions though, e.g. if multiple events happen in your UI at the same time, and you somewhere mistakenly assume the data won't change between unlock and the next lock of the datastate.

2 Likes