What is the proper way to use lazy_static variables safely and briefly

I have tried to extract the global variables from RwLock like the following code, but I am not sure if it is safe.

lazy_static::lazy_static! {
    static ref CONTEXT:RwLock<Option<AndroidContext>> = RwLock::new(None);
}

fn get_context<'a>() -> Result<
    (
        &'a AndroidContext,
        LockResult<RwLockReadGuard<'a, Option<AndroidContext>>>,
    ),
    types::Error,
> {
    let lock = CONTEXT.read();
    let context = lock
        .as_ref()
        .map_err(|e| types::Error::RLock(e.to_string()))
        .map(|context| -> Result<&'a AndroidContext, types::Error> {
            unsafe { std::mem::transmute(context.as_ref().ok_or(types::Error::NoPlatformContext)) }
        })??;
    Ok((context, lock))
}

fn get_mut_context<'a>() -> Result<
    (
        &'a mut AndroidContext,
        LockResult<RwLockWriteGuard<'a, Option<AndroidContext>>>,
    ),
    types::Error,
> {
    let mut lock = CONTEXT.write();
    let context = lock
        .as_mut()
        .map_err(|e| types::Error::WLock(e.to_string()))
        .map(|context| -> Result<&'a mut AndroidContext, types::Error> {
            unsafe { std::mem::transmute(context.as_mut().ok_or(types::Error::NoPlatformContext)) }
        })??;
    Ok((context, lock))
}

This is not safe, at least the mut part, since the returned exclusive reference is aliased with guard. Why can't you return LockResult directly?

2 Likes

because it is cumbersome to write a bunch of code to just extract the inner variable. I have tried to use macro but it it not suitable for this case when RWLock is really required,not just for the first time assignment.

I don't see how working with a guard object returned by locking the RwLock results in any more code that needs to be written. The guards are almost as easy to use as normal references, you can dereference them, call methods in them, and so on...

1 Like

It is true if just calling read().unwrap().as_ref().unwrap(), but the error handling is annoying

Error handling must be done once - when converting LockResult<T> to T. Afterwards, ReadGuard<T> is mostly like &T, and WriteGuard<T> is mostly like &mut T. No need to try and store the reference explicitly.

1 Like

I think I get your point, but I am not sure how to convert WriteGuard<Option<T>> to WriteGuard<T> with error handling once

I see. If the standard library offered something analogous to MappedRwLockWriteGuard in lock_api - Rust that would be easier.


You could follow various approaches here, e. g. switching to using lock_api somehow, defining a wrapper type that can be returned whose Deref(Mut) impl just does the unwrap implicitly, offering a reference only to a callback instead of via return values, using various self-referencing-datatype approaches (though that seems a bit overkill), ...

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.