Design Pattern - Singleton (TypeScript & Rust)
Tags: designpatterns, rust, typescript
Published: 2024-03-08 09:44:19

The singleton pattern has many applications in software development, such as a database connection pool, a globally unique dialog, a global logging tool, and more.

# TypeScript

```ts
class Singleton {
  private static instance: Singleton
  private data: string

  private constructor(data: string) {
    this.data = data
  }

  public static getInstance(data: string): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton(data)
    }
    return Singleton.instance
  }

  public getData(): string {
    return this.data
  }
}

const instance1 = Singleton.getInstance('hello')
const instance2 = Singleton.getInstance('world')

console.log(instance1 === instance2) // true
console.log(instance1.getData()) // hello
console.log(instance2.getData()) // hello
```

The implementation in TypeScript is as mentioned above, relatively simple. Now let's see how to implement it in Rust.

# Rust


After learning Rust for a day, it should be very easy to write the following code:

```rust
pub struct Singleton {}

static INSTANCE: &Singleton = &Singleton {};

pub fn get_instance() -> &'static Singleton {
    INSTANCE
}

let instance1 = get_instance();
let instance2 = get_instance();
println!("{:p} {:p}", instance1, instance2);
```

However, the code above is obvious invalid, as the `Singleton` struct does not have any parameter. Let's modify it by adding a parameter to `Singleton`:

```rust
pub struct Singleton {
    data: String,
}

static mut INSTANCE: &mut Singleton = &mut Singleton {
    data: "".to_string(),
};

pub fn get_instance() -> &'static mut Singleton {
    unsafe { INSTANCE }
}
```

However, it failed to compile:

```
   |
40 |     data: "".to_string(),
   |              ^^^^^^^^^^^
   |
   = note: calls in statics are limited to constant functions, tuple structs and tuple variants
   = note: consider wrapping this expression in `Lazy::new(|| ...)` from the `once_cell` crate: https://crates.io/crates/once_cell
```

So let's follow its prompt and try using the `once_cell` library, and add an `init` method for external data initialization.

```rust

#[derive(Default)]
pub struct Singleton {
    data: String,
}

impl Singleton {
    pub fn init(&mut self, data: String) {
        self.data = data
    }

    pub fn get_data(&self) -> &str {
        self.data.as_str()
    }
}

static mut INSTANCE: Lazy<Singleton> = Lazy::new(|| Singleton::default());

pub fn get_instance() -> &'static mut Singleton {
    unsafe { INSTANCE.deref_mut() }
}

let mut instance1 = get_instance();
let mut instance2 = get_instance();
instance1.init("hello".to_string());
instance2.init("world".to_string());
let d1 = instance1.get_data();
let d2 = instance2.get_data();
println!("{} {}", d1, d2); // world world
```

The compilation passed, but it clearly does not meet the requirements because the value of data is set to the parameter passed to by the last calling of `init` method, which is 'world'. Continuing to look at the documentation for `once_cell`, I found that it provides `set` and `get` methods, which can be used to store and access the instance of `Singleton`, and the instantiation code can be moved to the `get_instance` method:

```rust
use once_cell::sync::{OnceCell};

#[derive(Default, Debug)]
pub struct Singleton {
    data: String,
}

impl Singleton {
    pub fn new(data: String) -> Self {
        Self { data }
    }

    pub fn get_data(&self) -> &str {
        self.data.as_str()
    }
}

static INSTANCE: OnceCell<Singleton> = OnceCell::new();
pub fn get_instance(data: String) -> &'static Singleton {
    unsafe {
        if (INSTANCE.get().is_none()) {
            let mut instance = Singleton::new(data);
            INSTANCE.set(instance).expect("Failed to set");
        }

        &INSTANCE.get().unwrap()
    }
}

let mut instance1 = get_instance();
let mut instance2 = get_instance();
instance1.init("hello".to_string());
instance2.init("world".to_string());
let d1 = instance1.get_data();
let d2 = instance2.get_data();
println!("{} {}", d1, d2); // hello hello
```

It seems to work again, but don't get too excited. If we switch to the following test code, there will be a problem:

```rust
    let threads: Vec<_> = (0..10)
        .map(|i| {
            spawn(move || {
                get_instance(format!("hello{}", i));
            })
        })
        .collect();

    for handle in threads {
        handle.join().unwrap();
    }

    let instance = get_instance("".to_string());
    println!("{}", instance.get_data());
```

```
Failed to set: Singleton { data: "hello0" }Failed to set: Singleton { data: "hello1" }
```

The reason is that the statement `INSTANCE.get().is_none()` is not thread-safe. Multiple threads may enter the code block inside the if statement:

```rust
if (INSTANCE.get().is_none()) {

}
```

Let's add a lock then:

```rust
static mut INITIALIZED: Mutex<bool> = Mutex::new(false);
static INSTANCE: OnceCell<Singleton> = OnceCell::new();
pub fn get_instance(data: String) -> &'static Singleton {
    unsafe {
        let mut a = INITIALIZED.lock().unwrap();
        if (!*a) {
            let instance = Singleton::new(data);
            INSTANCE.set(instance).expect("Failed to set");
            *a = true;
        }

        &INSTANCE.get().unwrap()
    }
}
```

Now it can work properly again. Moreover, we can use `std::sync::Once` to make our code simpler:

```rust
static INIT: Once = Once::new();
static INSTANCE: OnceCell<Singleton> = OnceCell::new();

pub fn get_instance(data: String) -> &'static Singleton {
    INIT.call_once(|| {
        let instance = Singleton::new(data);
        INSTANCE.set(instance).expect("Fail to set");
    });

    INSTANCE.get().unwrap()
}

```

It's OK now? No! In certain scenarios, I need the `Singleton` instance to be thread-exclusive. For example, consider the following requirements:

```rust
pub fn log(&self, thread_name: String) {
    println!("{}: step1", thread_name);
    println!("{}: step2", thread_name);
}
```

When I invoke the `log` method in a particular thread, it is necessary to print `step1` and `step2` consecutively before printing logs from other threads. In this case, the aforementioned code cannot support this requirement. Therefore, it is necessary to lock the entire `Singleton` instance:

```rust
static INSTANCE: OnceCell<Mutex<Singleton>> = OnceCell::new();

pub fn get_instance(data: String) -> MutexGuard<'static, Singleton> {
    INIT.call_once(|| {
        let instance = Mutex::new(Singleton::new(data));
        INSTANCE.set(instance).expect("Fail to set instance");
    });

    INSTANCE.get().unwrap().lock().unwrap()
}

```

With this implementation, a multi-threaded singleton is achieved. The complete code is as follows:

```rust
// singleton.rs
use once_cell::sync::OnceCell;
use std::sync::{Mutex, MutexGuard, Once};

#[derive(Debug)]
pub struct Singleton {
    data: String,
}

impl Singleton {
    fn new(data: String) -> Self {
        Self { data }
    }

    pub fn get_data(&self) -> &str {
        &self.data
    }
    pub fn log(&self, thread_name: String) {
        println!("{}: step1", thread_name);
        println!("{}: step2", thread_name);
    }
}

static INIT: Once = Once::new();
static INSTANCE: OnceCell<Mutex<Singleton>> = OnceCell::new();

pub fn get_instance(data: String) -> MutexGuard<'static, Singleton> {
    INIT.call_once(|| {
        let instance = Mutex::new(Singleton::new(data));
        INSTANCE.set(instance).expect("Fail to set instance");
    });

    INSTANCE.get().unwrap().lock().unwrap()
}

// main.rs
let threads: Vec<_> = (0..10)
    .map(|i| {
        spawn(move || {
            let instance = get_instance(format!("hello{}", i));
            instance.log(format!("thread{}", i));
        })
    })
    .collect();

for handle in threads {
    handle.join().unwrap();
}

let instance = get_instance("".to_string());
println!("{}", instance.get_data());
```