Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ jobs:
- run: cargo run --bin factory-method-render-dialog
- run: cargo run --bin prototype
- run: cargo run --bin simple-factory
- run: cargo run --bin singleton
- run: cargo run --bin singleton-safe
- run: cargo run --bin singleton-lazy
# - run: cargo run --bin singleton-mutex # Requires Rust 1.63
- run: cargo run --bin singleton-once
- run: cargo run --bin singleton-logger
- run: cargo run --bin static-creation-method
- run: cargo run --bin adapter
- run: cargo run --bin bridge
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ members = [
"creational/factory-method/render-dialog",
"creational/prototype",
"creational/simple-factory",
"creational/singleton",
"creational/singleton/how-to-create",
"creational/singleton/logger",
"creational/static-creation-method",
"structural/adapter",
"structural/bridge",
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ cargo run --bin factory-method-maze-game
cargo run --bin factory-method-render-dialog
cargo run --bin prototype
cargo run --bin simple-factory
cargo run --bin singleton
cargo run --bin singleton-safe
cargo run --bin singleton-lazy
cargo run --bin singleton-mutex # Requires Rust 1.63
cargo run --bin singleton-once
cargo run --bin singleton-logger
cargo run --bin static-creation-method
cargo run --bin adapter
cargo run --bin bridge
Expand Down
95 changes: 4 additions & 91 deletions creational/singleton/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,94 +3,7 @@
_**Singleton** lets you ensure that only one object of its kind exists,
while providing a global access point to this instance._

💡 Singleton is a _global mutable object_, and in terms of **Rust**
it is a _`static mut` item_ which in turn means
[it requires an **`unsafe`** block](https://doc.rust-lang.org/reference/items/static-items.html#mutable-statics)
either reading or writing a mutable static variable.

On one side, it can be treated as an unsafe pattern, however,
on the other side, Singleton is used in Rust on practice. A good **read-world**
example of Singleton is `log` crate that introduces `log!`, `debug!` and other
logging macro which you can use throughout your code **after** setting up a concrete
logger instance, e.g. [env_logger](https://crates.io/crates/env_logger).
(`env_logger` uses
[log::set_boxed_logger](https://docs.rs/log/latest/log/fn.set_boxed_logger.html)
under the hood, which has an `unsafe` block to set up a global logger object).

💡 Starting with **Rust 1.63**, `Mutex::new` is `const`, you can use global
static `Mutex` locks without needing lazy initialization. See the `mutex.rs`
example below.

## `safe.rs`

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()`.

### How to Run

```bash
cargo run --bin singleton
```

### Output

```
Final state: 1
```

## `lazy.rs`

This is a singleton implementation via `lazy_static!`.

`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.

### How to Run

```bash
cargo run --bin singleton-lazy
```

### Output

```
Called 3
```

## `mutex.rs`

⚠ Starting with `rustc 1.63`.

> 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.

```rust
use std::sync::Mutex;

static GLOBAL_DATA: Mutex<Vec<i32>> = Mutex::new(Vec::new());
```

### How to Run

```bash
cargo run --bin singleton-mutex
```

### Output

```
Called 3 times
```

## Notes

1. In order to provide safe and usable access to a singleton object,
introduce an API hiding `unsafe` blocks under the hood.
2. See a thread about a mutable Singleton on Stackoverflow:
https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton.
- [`how-to-create`](./how-to-create/) contains simple examples how to create
an actual singleton object.
- [`logger`](./logger/) is a practical example of how the Rust logging
subsystem is essentially implemented.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ version = "0.1.0"

[dependencies]
lazy_static = "1.4.0"
once_cell = "1.15"

[[bin]]
name = "singleton"
name = "singleton-safe"
path = "safe.rs"

[[bin]]
Expand All @@ -17,3 +18,7 @@ path = "lazy.rs"
[[bin]]
name = "singleton-mutex"
path = "mutex.rs"

[[bin]]
name = "singleton-once"
path = "once.rs"
119 changes: 119 additions & 0 deletions creational/singleton/how-to-create/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Singleton

_**Singleton** lets you ensure that only one object of its kind exists,
while providing a global access point to this instance._

Singleton is a _global mutable object_, and in terms of **Rust**
it is a _`static mut` item_ which in turn
[requires an **`unsafe`** block](https://doc.rust-lang.org/reference/items/static-items.html#mutable-statics)
for either reading or writing a mutable static variable.

For this reason, the Singleton pattern can be perceived as unsafe. However,
the pattern is still widely used in practice. A good read-world example of
Singleton is a `log` crate that introduces `log!`, `debug!` and other logging
macros, which you can use throughout your code after setting up a concrete
logger instance, such as [env_logger](https://crates.io/crates/env_logger).
As we can see, `env_logger` uses
[log::set_boxed_logger](https://docs.rs/log/latest/log/fn.set_boxed_logger.html)
under the hood, which has an unsafe block to set up a global logger object.

- In order to provide safe and usable access to a singleton object,
introduce an API hiding unsafe blocks under the hood.
- See the thread about a mutable Singleton on Stackoverflow for more information.

There is a plenty of useful containers that allows to avoid an `unsafe` block
in your code:

1. [once_cell::sync::OnceCell](https://docs.rs/once_cell/latest/once_cell/sync/struct.OnceCell.html)
2. [lazy_static::lazy_static](https://docs.rs/lazy_static/latest/lazy_static)
3. [std::sync::Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html)

💡 Starting with **Rust 1.63**, `Mutex::new` is `const`, you can use global
static `Mutex` locks without needing lazy initialization. See the `mutex.rs`
example below.

## `safe.rs`

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()`.

### How to Run

```bash
cargo run --bin singleton-safe
```

### Output

```
Final state: 1
```

## `lazy.rs`

This is a singleton implementation via `lazy_static!`.

`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 free of explicit `unsafe` blocks.

### How to Run

```bash
cargo run --bin singleton-lazy
```

### Output

```
Called 3
```

## `once.rs`

`OnceCell` allows having a custom initialization of a singleton at an
**arbitrary place** unlike `lazy_static!`, where the initialization must be
placed in a static block. `Mutex` is still needed there to make an actual object
modifiable without an `unsafe` block.

### How to Run

```bash
cargo run --bin singleton-once
```

### Output

```
[42, 1, 1, 1]
```

## `mutex.rs`

⚠ Starting with `rustc 1.63`.

> 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.

```rust
use std::sync::Mutex;

static GLOBAL_DATA: Mutex<Vec<i32>> = Mutex::new(Vec::new());
```

### How to Run

```bash
cargo run --bin singleton-mutex
```

### Output

```
Called 3 times
```
27 changes: 27 additions & 0 deletions creational/singleton/how-to-create/once.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! `OnceCell` allows having a custom initialization of a singleton at
//! an arbitrary place. The initialization can be done only once.
//! `Mutex` is still needed to make an actual object modifiable
//! without an `unsafe` block.

use once_cell::sync::OnceCell;
use std::sync::Mutex;

static ARRAY: OnceCell<Mutex<Vec<i32>>> = OnceCell::new();

fn singleton_init(array: Vec<i32>) {
ARRAY.get_or_init(|| Mutex::new(array));
}

fn do_a_call() {
ARRAY.get().unwrap().lock().unwrap().push(1);
}

fn main() {
singleton_init(vec![42]);

do_a_call();
do_a_call();
do_a_call();

println!("{:?}", ARRAY.get().unwrap().lock().unwrap());
}
11 changes: 11 additions & 0 deletions creational/singleton/logger/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
edition = "2021"
name = "singleton-logger"
version = "0.1.0"

[[bin]]
name = "singleton-logger"
path = "main.rs"

[dependencies]
once_cell = "1.15"
26 changes: 26 additions & 0 deletions creational/singleton/logger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Logger

A practical example of how the Rust logging subsystem is essentially implemented using the Singleton pattern.

- [`log.rs`](./log.rs) - a lightweight logging facade encapsulating a static
logger object (singleton); the module is a representation of
the [`log`](https://docs.rs/log/latest/log/) crate,
- [`simple_logger.rs`](./simple_logger.rs) - a logger implementation printing
into _stdout_, it represents the [`simplelog`](https://docs.rs/simplelog/latest/simplelog/)
crate,
- [`app.rs`](./app.rs) - an arbitrary code using a logging facade,
- [`main.rs`](./main.rs) - application initialization

## How to Run

```bash
cargo run --bin singleton-logger
```

## Output

```bash
[I] Application starts
[W] Something non-critical happened
[E] Execution failed
```
7 changes: 7 additions & 0 deletions creational/singleton/logger/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use crate::log::{error, info, warn};

pub fn run() {
info("Application starts");
warn("Something non-critical happened");
error("Execution failed")
}
Loading