Static Struct (with a String) inside a module for singleton pattern

I have been trying to do this for some time now. I have to say that I find Rust really hard to understand. Why is this soooo hard?

I can do this in Java and Go and I have no difficulty understanding the code.

All I want is a Struct on the heap containing a String (on the heap) where the struct is read from JSON and
is available for the life of a module or program with a way to access it!

I have tried using Option<My_Struct> but I get tripped up by lifetimes. I have tried to define these but cause other compiletime issues.

I have tried &str in my struct but I cannot create it at compile time so that was no good!

I have googled the answer but the examples either do not work or are incomplete.

Could somome please help and explain what I need too do. I really like the ideas behind rust but if it makes it this complex I think Go will be a better solution for the things i need to do.

my sudo code:

 struct My_Struct{
   name: String,
   port: i32
 }

 static <ref_to My_Struct>
 
 fn get_instance() -> My_Struct {
 	if <ref_to My_Struct> is_not_defined {
 		<ref_to My_Struct> = <use serde to read My_Struct from JSON file>
 	}
	return <ref_to My_Struct>
 }

I want My_Struct to be accessible from all modules via mod::get_instance().

I should only ever create My_Struct once.

The scope of My_Struct is the life of the application.

The lazy_static crate is built for this purpose.

struct MyStruct {
  name: String,
  port: i32
}

lazy_static! {
    static ref MY_GLOBAL: MyStruct = {
        let file = File::open("foobar.txt").unwrap();
        serde_json::from_reader(file).unwrap()
    };
}

You can now access it directly through MY_GLOBAL. This will initialize it on first use, and only once.

Have you considered lazy_static? https://docs.rs/lazy_static/1.4.0/lazy_static/

So basically unless you use a crate this cannot be done!

As a developer I find that unsettling. If I cannot work out how to do it, and it seems quite a basic process then how can I trust the application I have written.

I am sure that Lazy_static is fine, it is not a specific complaint it is a principal. How does it work? Why is this feature so complex that I cannot write it myself?

I now need to get the code for Lazy_static and spent time picking it apart.

The lazy_static crate is a highly trusted and very widely used crate, but if you want to do it with just the standard library, then you're looking for the std::sync::Once.

The lazy_static is slightly more complex than the example of Once since it uses the Deref trait to create a value that is initialized on first use, as opposed to a function for obtaining the value like you see in the documentation for Once. It's also a macro, which makes it a bit more cryptic.

The main interesting part of lazy_static is found in the inline_lazy file: link. Note that it uses a Cell as opposed to a static mut, which I also recommend that you do, as static mut is generally not recommended to ever use because of how difficult it is to use correctly.

To add to that: the reason it's hard to do in Rust is because it is hard, and Rust doesn't let you do the wrong thing. Yes, it's "easy" in Java or Go or other languages with shared mutability. But that's only because they let you get away with a lot of dangerous or even outright incorrect code, and they hide the fact that the result won't do what you think it does.

Eg. the pattern that you wanted to use is "if not initialized, initialize, then return". This naïve approach unto itself can be the basis for a race condition unless properly snychronized (which is hard to do).

The reason this pattern is codified as a crate is that it's better to write and audit once and then re-use the code than requiring every individual developer to write it correctly.

By the way, lazy_static is currently being considered for inclusion in std.

5 Likes

A big premise of the language is to prevent data races and unsound code, one of the main ways it does this is by ensuring only one thing has mutable access to something at a time. A static variable that could be mutated at any time by any code which is able to access it is in direct contradiction of this.You can have mutable static variables by using unsafe, but then you're opening yourself up to the data races you would normally get in Go or Java.

If you still want to use singletons though, there's a fairly comprehensive answer on StackOverflow:

As an alternative, does your code need to enforce that a type is only created once?

If your code needs to ensure things are initialized and have access to certain data, a common pattern is to create some sort of Context type (e.g. libsignal_protocol::Context) which all related functions/objects take as a parameter. That way the caller can create this object in main() and pass out references to it.

1 Like

It's not so hard and it's not unsafe!

This is config data. It is loaded on application load and it is immutable (or did I forget to say that!) and I agree that you need to be aware of the issues when the above does not apply.

I am still new to Rust and finding the learning curve steep!
I guess it's all the Java and Go that is holding me back. So much to unlearn!

Thank for the comments and feedback.

Example: Double-checked locking and the Singleton pattern

This article refers to the Java Memory Model before it was revised for Java 5.0; statements about memory ordering may no longer be correct. However, the double-checked locking idiom is still broken under the new memory model.

Opinions: Singletons considered stupid, Flaw: Brittle Global State & Singletons

Alternative: Just Create One which amounts to simply injecting a configuration dependency as already outlined by @Michael-F-Bryan.

2 Likes