Ben Chuanlong Du's Blog

It is never too late to learn.

Implement a Singleton in Rust

Things on this page are fragmentary and immature notes/thoughts of the author. Please read with your own judgement!

Tips and Traps

  1. std::cell::OnceCell is a partial implementation of once_cell in the Rust standard library.

  2. There are 2 Rust crates lazy_static and once_cell for (lazy) once assignment (which can be used to create singletons). once_cell is preferred to lazy_static for a few reasons .

    • once_cell is both more flexible and more convenient than lazy_static .

    • Unlike once_cell , lazy_static supports spinlock-based implementation of blocking which works with #![no_std].

    • once_cell will likely be part of the Rust standard library in future.

  3. Once assignment means that you can only assign value to such a variable once. However, it is still possible to mutate the variable after initialization.

  4. When defining a static variable which implements Copy, you can define it as a static reference. This helps avoiding accidental implicity copies. The more idiomatic way is to define a wrapper non-Copy struct so that you can be sure that the underlying data won't get copied.

In [2]:
:timing
:sccache 1
:dep once_cell = "1.13.0"
Out[2]:
Timing: true
sccache: true
Out[2]:
Took 17831ms
In [3]:
use std::{sync::Mutex, collections::HashMap};
use once_cell::sync::Lazy;
Out[3]:
Took 172ms

Create a Mutable Static Variable (Singleton)

In [4]:
static GLOBAL_DATA_1: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| {
    let mut m = HashMap::new();
    m.insert(13, "Spica".to_string());
    m.insert(74, "Hoyten".to_string());
    Mutex::new(m)
});
Out[4]:
Took 346ms
In [16]:
GLOBAL_DATA_1.lock().unwrap().insert(1, "New change".to_string());
GLOBAL_DATA_1.lock().unwrap().get(&1)
Out[16]:
Some("New change")

Create a Immutable Static Variable (Singleton)

Mutex allows threads to mutate the static variable after initialization. It is not necessary if you just need a readable static variable (after initialization).

In [5]:
static GLOBAL_DATA_2: Lazy<HashMap<i32, String>> = Lazy::new(|| {
    let mut m = HashMap::new();
    m.insert(13, "Spica".to_string());
    m.insert(74, "Hoyten".to_string());
    m
});
Out[5]:
Took 261ms
In [6]:
GLOBAL_DATA_2.insert(1, "New change".to_string());
GLOBAL_DATA_2.get(&1)
[E0596] Error: cannot borrow immutable static item `GLOBAL_DATA_2` as mutable
   [command_6:1:1]
   
 1 │ GLOBAL_DATA_2.insert(1, "New change".to_string());
   ·   
   ·                          cannot borrow as mutable
   · 
   · Note: You can change an existing variable to mutable like: `let mut x = x;`
───╯
In [8]:
GLOBAL_DATA_2.get(&13)
Out[8]:
Some("Spica")
Out[8]:
Took 478ms

Lazy Referencing Lazy

In [13]:
static GLOBAL_DATA_3: Lazy<[Lazy<&[usize]>; 2]> = Lazy::new(|| {
    let mut arr: [Lazy<&[usize]>; 2] = [
        Lazy::new(|| &[]),
        Lazy::new(|| &[]),
    ];
    arr[0] = Lazy::new(|| &[1, 2, 3]);
    arr[1] = Lazy::new(|| &[4, 5, 6, 7]);
    arr
});
Out[13]:
Took 242ms
In [14]:
GLOBAL_DATA_3
Out[14]:
Lazy { cell: OnceCell(Uninit), init: ".." }
Out[14]:
Took 408ms
In [24]:
let x1: &[usize] = &GLOBAL_DATA_3[0];
x1
Out[24]:
[1, 2, 3]
Out[24]:
Took 555ms
In [23]:
let x2: &[usize] = &GLOBAL_DATA_3[1];
x2
Out[23]:
[4, 5, 6, 7]
Out[23]:
Took 576ms
In [ ]:

Comments