Although the mechanism of Rust Lifetime suggest people using local variable guaranteed to ownership is correct, we inevitably need to use global variables in some situations. Rust designer have added many limitations to guarantee that global variables are safe in terms of memory and threading, compared to other languages such as C/C++ and Python. These limitations indeed help developers avoid lots of crash or unexpected issues that are difficult to detect, but it also significantly increases the difficulty of programming. So, in this post, I will introduce some approachs to create global variable and singleton elegantly in various situtaions.
const and static
In order to define a global variable, we have to use const or static rather than let, so we should understand the difference between const and static.
const
- Evaluation: compile time
- Recv: Constant Expression
- Memory: does not occupy any memory space (inline position)
- Lifetime: in their scope
- explicit type annotation
Rust’s const is similar with #define in C language,
static
- Evaluation: compile time
- Recv: Constant Expression
- Memory: global single entity will be saved in a read-only memory area
- Lifetime: ‘static, equal infinite life time, drop() never be called
- explicit type annotation
If we want to create a mutable global variable, we should use static rather than const and let.
Global Variable
Next, I will introduce how to create global variables based on different situation.
Immutable-Primitive-Type
|
|
Mutable-Primitive-Type
If a variable is mutable, we can not use the const keyword to define it. And we will face the issue of thread safety with mutable variable.
If you ensure that your global variable is thread-safe, you can define it as follows:
|
|
Or use Mutex
|
|
Mutable-Collection-Type
We will not discuss immutable collection type global variables. I will introduce some common approachs to define a global collection type.
|
|
This seems straightforward right? However, if we replace Vec with HashSet, we will encounter an error:
cannot call non-const fn `HashSet::<String>::new` in statics
calls in statics are limited to constant functions, tuple structs and tuple variants
The reason for this problem is that the function signature of new() in HashSet does not include const, but a static variable must be initialized with a Constant Expression! Therefore, we need to make some modifications to define a HashSet global variable.
|
|
Using once_cell::unsync::Lazy, it is possible to have statics that require code to be executed at runtime in order to be initialized. This includes anything requiring heap allocations, like vectors or hash maps, as well as anything that requires non-const function calls to be computed.
We also can use the following approach to it to initialize our global variables:
|
|
Singleton
Lazy+Mutex
Like the approach above, we can use Lazy and Mutex to initialize a instance of the class to implement a singleton.
|
|
lazy_static
This is a common approach for implementing the singleton patterns in Rust. We don’t need to create an additional independent global variable.
|
|
std::sync::OnceLock
We have to add extern crate to the project to implement Lazy and lazy_static. However, in later versions of Rust(1.70+), Rust introduced OnceLock, which allows us to make the code more refined.
|
|
Compiling above code, you should encounter errors:
`*mut c_void` cannot be sent between threads safely
within `Singleton`, the trait `Send` is not implemented for `*mut c_void`
required for `Mutex<Singleton>` to implement `Sync`
The reason why the compiler raises errors is that the type of member variable handle —— *mut c_void —— does not implement the Send and the Sync trait, so the compiler considers it not thread-safe. To solve this problem, we need to clarify whether the class is thread-safe or not. In this case, the instance of Singleton will have its drop() method called, so if we guarantee that the member variable handle cannot be modified, this class is thread-safe. We can implement the Send and the Sync trait for Singleton to tell the compiler that this class is thread-safe, to solve this problem.
|
|