Singleton Pattern
Singleton is a creational design pattern, which ensures that only one object of its kind exists and provides a single point of access to it for any other code.
Singleton has almost the same pros and cons as global variables. Although they’re super-handy, they break the modularity of your code.
You can’t just use a class that depends on a Singleton in some other context, without carrying over the Singleton to the other context. Most of the time, this limitation comes up during the creation of unit tests.
Golang Implementation
Example 1: DCL (Double-Checked Locking)
Usually, a singleton instance is created when the struct is first initialized. To make this happen, we define the getInstance method on the struct. This method will be responsible for creating and returning the singleton instance. Once created, the same singleton instance will be returned every time the getInstance is called.
How about goroutines? The singleton struct must return the same instance whenever multiple goroutines are trying to access that instance. Because of this, it’s very easy to get the singleton design pattern implemented wrong. The example below illustrates the right way to create a singleton.
Some points worth noting:
There is a nil-check at the start for making sure singleInstance is empty first time around. This is to prevent expensive lock operations every time the getinstance method is called. If this check fails, then it means that the singleInstance field is already populated.
The singleInstance struct is created within the lock.
There is another nil-check after the lock is acquired. This is to ensure that if more than one goroutine bypasses the first check, only one goroutine can create the singleton instance. Otherwise, all goroutines will create their own instances of the singleton struct.
single.go
package main
import (
"fmt"
"sync"
)
var lock = &sync.Mutex{}
type single struct {
}
var singleInstance *single
func getInstance() *single {
// DCL: Double Checked Locking
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
} else {
fmt.Println("Single instance already created.")
}
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
main.go
package main
import (
"fmt"
)
func main() {
for i := 0; i < 5; i++ {
go getInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}
// Single instance already created.
// Single instance already created.
// Single instance already created.
// Single instance already created.
Example 2:
There are other methods of creating a singleton instance in Go:
init function
We can create a single instance inside the init function. This is only applicable if the early initialization of the instance is ok. The init function is only called once per file in a package, so we can be sure that only a single instance will be created.
sync.Once
The sync.Once will only perform the operation once. See the code below:
syncOnce.go
package main
import (
"fmt"
"sync"
)
var once sync.Once
type single struct {
}
var singleInstance *single
func getInstance() *single {
if singleInstance == nil {
// only one thread can enter this block, others are blocked until the one leaves the block
// once the one entered into the once.Do block leaves, the others will
// no longer blocked and will execute again but bypass the once.Do block
once.Do(
// here we are using sync.Once to ensure that the function is called only once
func() {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
})
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
main.go
package main
import (
"fmt"
"runtime"
)
func main() {
for i := 0; i < 50; i++ {
go getInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
// print existing go routines
fmt.Println("Existing go routines: ", runtime.NumGoroutine())
}
// Creating single instance now.
// Single instance already created. [uncertain times output]
Rust Implementation
Example 1: Safe Singleton
A pure safe way to implement Singleton in Rust is using no global variables at all and passing everything around through function arguments. The oldest living variable is an object created at the start of the main().
main.rs
//! A pure safe way to implement Singleton in Rust is using no static variables
//! and passing everything around through function arguments.
//! The oldest living variable is an object created at the start of the `main()`.
fn change(global_state: &mut u32) {
*global_state += 1;
}
fn main() {
let mut global_state = 0u32; // value is 0 with type u32
change(&mut global_state);
println!("Final state: {}", global_state);
}
// Final state: 1
Example 2: Lazy Singleton
This is a singleton implementation via lazy_static! (a crate), which allows declaring a static variable with lazy initialization at first access. It is actually implemented via unsafe with static mut manipulation, however, it keeps your code clear of unsafe blocks.
main.rs
//! Taken from: https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton
//!
//! Rust doesn't really allow a singleton pattern without `unsafe` because it
//! doesn't have a safe mutable global state.
//!
//! `lazy-static` allows declaring a static variable with lazy initialization
//! at first access. It is actually implemented via `unsafe` with `static mut`
//! manipulation, however, it keeps your code clear of `unsafe` blocks.
//!
//! `Mutex` provides safe access to a single object.
use lazy_static::lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec![]);
}
fn do_a_call() {
ARRAY.lock().unwrap().push(1);
}
fn main() {
do_a_call();
do_a_call();
do_a_call();
println!("Called {}", ARRAY.lock().unwrap().len());
}
// Called 3
Example 3: Singleton using Mutex
Starting with Rust 1.63, it can be easier to work with global mutable singletons, although it’s still preferable to avoid global variables in mostcases.
Now that Mutex::new is const, you can use global static Mutex locks without needing lazy initialization.
main.rs
//! ructc 1.63
//! https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton
//!
//! Starting with Rust 1.63, it can be easier to work with global mutable
//! singletons, although it's still preferable to avoid global variables in most
//! cases.
//!
//! Now that `Mutex::new` is `const`, you can use global static `Mutex` locks
//! without needing lazy initialization.
use std::sync::Mutex;
static ARRAY: Mutex<Vec<i32>> = Mutex::new(Vec::new());
fn do_a_call() {
ARRAY.lock().unwrap().push(1);
}
fn main() {
do_a_call();
do_a_call();
do_a_call();
let array = ARRAY.lock().unwrap();
println!("Called {} times: {:?}", array.len(), array);
drop(array);
*ARRAY.lock().unwrap() = vec![3, 4, 5];
println!("New singleton object: {:?}", ARRAY.lock().unwrap());
}
// Called 3 times: [1, 1, 1]
// New singleton object: [3, 4, 5]