How to create Global static variable?

There are several fucntions/ structs in the app that will use the object aws_utils so to me it makes sense to create a static variable of it during the server startup and keep using this variable every time i need aws_utils object. So i created it as follows

pub struct AwsUtilsSt {
    client: s3::Client,
}
static mut aws_utils: Option<AwsUtilsSt> = None;

impl AwsUtilsSt {
    pub async fn new() -> Self {
        let shared_config = aws_config::load_from_env().await;
        if aws_utils.is_none(){
            // ERROR here
            AwsUtilsSt {
                 client: S3Client::new(&shared_config),
            }
        }
       

}

Is there a way to fix this with out using unsafe blocks? Essentially i want to initialize the static variable only on init.

error[E0133]: use of mutable static is unsafe and requires unsafe function or block
   --> src/aws_utils.rs:217:12
    |
217 |         if aws_utils.is_none(){
    |            ^^^^^^^^^^^^^^^^^^^ use of mutable static

I think the crates lazy_static or once_cell might do what you want. Not sure which is better, and I'm also curious to understand how these differ.

Edit: Found this FAQ comparing them.

There is also the unstable std::sync::OnceLock or unstable std::cell::OnceCell.

1 Like

Will the once_cell::unsync::OnceCell do the job for you?

I think there's not much reason for using lazy_static because once_cell::sync::Lazy has the same functionality without requiring a macro. Given that once_cell is also more general, and that its API is being considered for inclusion in std, it is probably the unambiguously better choice.

7 Likes

Do I understand right that once_cell::race is not part of the inclusion plans for std (tracking issue #74465)?

I'm particularly curious about OnceBox. This doesn't seem to be implementable using atomics without unsafe. Should OnceBox be considered faster than once_cell::sync::OnceCell? Are there some downsides to it, except for the possibility of executing the initialization code more than once?

Sorry, I don't know whether or not it is.

I'm not sure. However, when shared-memory concurrency is being used, I find it almost completely useless to talk about this kind of micro-optimization. Unless you are using shared-memory concurrency in a way that it should not be used in a modern programming style (i.e., heavy contention), the cost of locks and atomics is almost always trivial.

Some anecdata for context: in a simple experiment I performed a couple of months ago, I was trying to find out whether a custom SQLite wrapper I was writing could have benefited from a single-threaded unsafe usage scheme. This would have allowed me to turn off mutexes in the library (as well as in the FFI-wrapping code) completely. I was unable to measure any meaningful difference between mutex-protected and unprotected access to the library when contention did not occur; so much so that even a trivial in-memory query (like select foo from bar limit 1) dominated the time spent in locking.

If I understand right, then neither OnceCell nor OnceBox use locking after the cell/box is initialized. They both seem to do an acquire load first, and if that load indicates that the cell is initialized, there will be no locking involved (also in case of OnceCell). So I guess OnceBox doesn't really have a huge advantage over OnceCell. Maybe extracting the value is a bit faster with OnceBox, but like you said it's probably barely noticable. Perhaps on embedded devices, code (program/binary) size might be smaller for OnceBox.

No race for now.

2 Likes

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.