Skip to content

Commit c5c8372

Browse files
authored
Singleton: add OnceCell and the logger example (#8)
1 parent a4ed510 commit c5c8372

File tree

16 files changed

+303
-95
lines changed

16 files changed

+303
-95
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ jobs:
3838
- run: cargo run --bin factory-method-render-dialog
3939
- run: cargo run --bin prototype
4040
- run: cargo run --bin simple-factory
41-
- run: cargo run --bin singleton
41+
- run: cargo run --bin singleton-safe
4242
- run: cargo run --bin singleton-lazy
4343
# - run: cargo run --bin singleton-mutex # Requires Rust 1.63
44+
- run: cargo run --bin singleton-once
45+
- run: cargo run --bin singleton-logger
4446
- run: cargo run --bin static-creation-method
4547
- run: cargo run --bin adapter
4648
- run: cargo run --bin bridge

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ members = [
1818
"creational/factory-method/render-dialog",
1919
"creational/prototype",
2020
"creational/simple-factory",
21-
"creational/singleton",
21+
"creational/singleton/how-to-create",
22+
"creational/singleton/logger",
2223
"creational/static-creation-method",
2324
"structural/adapter",
2425
"structural/bridge",

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ cargo run --bin factory-method-maze-game
5858
cargo run --bin factory-method-render-dialog
5959
cargo run --bin prototype
6060
cargo run --bin simple-factory
61-
cargo run --bin singleton
61+
cargo run --bin singleton-safe
6262
cargo run --bin singleton-lazy
6363
cargo run --bin singleton-mutex # Requires Rust 1.63
64+
cargo run --bin singleton-once
65+
cargo run --bin singleton-logger
6466
cargo run --bin static-creation-method
6567
cargo run --bin adapter
6668
cargo run --bin bridge

creational/singleton/README.md

Lines changed: 4 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,94 +3,7 @@
33
_**Singleton** lets you ensure that only one object of its kind exists,
44
while providing a global access point to this instance._
55

6-
💡 Singleton is a _global mutable object_, and in terms of **Rust**
7-
it is a _`static mut` item_ which in turn means
8-
[it requires an **`unsafe`** block](https://doc.rust-lang.org/reference/items/static-items.html#mutable-statics)
9-
either reading or writing a mutable static variable.
10-
11-
On one side, it can be treated as an unsafe pattern, however,
12-
on the other side, Singleton is used in Rust on practice. A good **read-world**
13-
example of Singleton is `log` crate that introduces `log!`, `debug!` and other
14-
logging macro which you can use throughout your code **after** setting up a concrete
15-
logger instance, e.g. [env_logger](https://crates.io/crates/env_logger).
16-
(`env_logger` uses
17-
[log::set_boxed_logger](https://docs.rs/log/latest/log/fn.set_boxed_logger.html)
18-
under the hood, which has an `unsafe` block to set up a global logger object).
19-
20-
💡 Starting with **Rust 1.63**, `Mutex::new` is `const`, you can use global
21-
static `Mutex` locks without needing lazy initialization. See the `mutex.rs`
22-
example below.
23-
24-
## `safe.rs`
25-
26-
A pure safe way to implement Singleton in Rust is using no global variables
27-
at all and passing everything around through function arguments.
28-
The oldest living variable is an object created at the start of the `main()`.
29-
30-
### How to Run
31-
32-
```bash
33-
cargo run --bin singleton
34-
```
35-
36-
### Output
37-
38-
```
39-
Final state: 1
40-
```
41-
42-
## `lazy.rs`
43-
44-
This is a singleton implementation via `lazy_static!`.
45-
46-
`lazy-static` allows declaring a static variable with lazy initialization
47-
at first access. It is actually implemented via `unsafe` with `static mut`
48-
manipulation, however, it keeps your code clear of `unsafe` blocks.
49-
50-
### How to Run
51-
52-
```bash
53-
cargo run --bin singleton-lazy
54-
```
55-
56-
### Output
57-
58-
```
59-
Called 3
60-
```
61-
62-
## `mutex.rs`
63-
64-
⚠ Starting with `rustc 1.63`.
65-
66-
> Starting with `Rust 1.63`, it can be easier to work with global mutable
67-
> singletons, although it's still preferable to avoid global variables in most
68-
> cases.
69-
>
70-
> Now that `Mutex::new` is `const`, you can use global static `Mutex` locks
71-
> without needing lazy initialization.
72-
73-
```rust
74-
use std::sync::Mutex;
75-
76-
static GLOBAL_DATA: Mutex<Vec<i32>> = Mutex::new(Vec::new());
77-
```
78-
79-
### How to Run
80-
81-
```bash
82-
cargo run --bin singleton-mutex
83-
```
84-
85-
### Output
86-
87-
```
88-
Called 3 times
89-
```
90-
91-
## Notes
92-
93-
1. In order to provide safe and usable access to a singleton object,
94-
introduce an API hiding `unsafe` blocks under the hood.
95-
2. See a thread about a mutable Singleton on Stackoverflow:
96-
https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton.
6+
- [`how-to-create`](./how-to-create/) contains simple examples how to create
7+
an actual singleton object.
8+
- [`logger`](./logger/) is a practical example of how the Rust logging
9+
subsystem is essentially implemented.

creational/singleton/Cargo.toml renamed to creational/singleton/how-to-create/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ version = "0.1.0"
55

66
[dependencies]
77
lazy_static = "1.4.0"
8+
once_cell = "1.15"
89

910
[[bin]]
10-
name = "singleton"
11+
name = "singleton-safe"
1112
path = "safe.rs"
1213

1314
[[bin]]
@@ -17,3 +18,7 @@ path = "lazy.rs"
1718
[[bin]]
1819
name = "singleton-mutex"
1920
path = "mutex.rs"
21+
22+
[[bin]]
23+
name = "singleton-once"
24+
path = "once.rs"
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Singleton
2+
3+
_**Singleton** lets you ensure that only one object of its kind exists,
4+
while providing a global access point to this instance._
5+
6+
Singleton is a _global mutable object_, and in terms of **Rust**
7+
it is a _`static mut` item_ which in turn
8+
[requires an **`unsafe`** block](https://doc.rust-lang.org/reference/items/static-items.html#mutable-statics)
9+
for either reading or writing a mutable static variable.
10+
11+
For this reason, the Singleton pattern can be perceived as unsafe. However,
12+
the pattern is still widely used in practice. A good read-world example of
13+
Singleton is a `log` crate that introduces `log!`, `debug!` and other logging
14+
macros, which you can use throughout your code after setting up a concrete
15+
logger instance, such as [env_logger](https://crates.io/crates/env_logger).
16+
As we can see, `env_logger` uses
17+
[log::set_boxed_logger](https://docs.rs/log/latest/log/fn.set_boxed_logger.html)
18+
under the hood, which has an unsafe block to set up a global logger object.
19+
20+
- In order to provide safe and usable access to a singleton object,
21+
introduce an API hiding unsafe blocks under the hood.
22+
- See the thread about a mutable Singleton on Stackoverflow for more information.
23+
24+
There is a plenty of useful containers that allows to avoid an `unsafe` block
25+
in your code:
26+
27+
1. [once_cell::sync::OnceCell](https://docs.rs/once_cell/latest/once_cell/sync/struct.OnceCell.html)
28+
2. [lazy_static::lazy_static](https://docs.rs/lazy_static/latest/lazy_static)
29+
3. [std::sync::Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html)
30+
31+
💡 Starting with **Rust 1.63**, `Mutex::new` is `const`, you can use global
32+
static `Mutex` locks without needing lazy initialization. See the `mutex.rs`
33+
example below.
34+
35+
## `safe.rs`
36+
37+
A pure safe way to implement Singleton in Rust is using NO global variables
38+
at all and passing everything around through function arguments.
39+
The oldest living variable is an object created at the start of the `main()`.
40+
41+
### How to Run
42+
43+
```bash
44+
cargo run --bin singleton-safe
45+
```
46+
47+
### Output
48+
49+
```
50+
Final state: 1
51+
```
52+
53+
## `lazy.rs`
54+
55+
This is a singleton implementation via `lazy_static!`.
56+
57+
`lazy-static` allows declaring a static variable with lazy initialization
58+
at first access. It is actually implemented via `unsafe` with `static mut`
59+
manipulation, however, it keeps your code free of explicit `unsafe` blocks.
60+
61+
### How to Run
62+
63+
```bash
64+
cargo run --bin singleton-lazy
65+
```
66+
67+
### Output
68+
69+
```
70+
Called 3
71+
```
72+
73+
## `once.rs`
74+
75+
`OnceCell` allows having a custom initialization of a singleton at an
76+
**arbitrary place** unlike `lazy_static!`, where the initialization must be
77+
placed in a static block. `Mutex` is still needed there to make an actual object
78+
modifiable without an `unsafe` block.
79+
80+
### How to Run
81+
82+
```bash
83+
cargo run --bin singleton-once
84+
```
85+
86+
### Output
87+
88+
```
89+
[42, 1, 1, 1]
90+
```
91+
92+
## `mutex.rs`
93+
94+
⚠ Starting with `rustc 1.63`.
95+
96+
> Starting with `Rust 1.63`, it can be easier to work with global mutable
97+
> singletons, although it's still preferable to avoid global variables in most
98+
> cases.
99+
>
100+
> Now that `Mutex::new` is `const`, you can use global static `Mutex` locks
101+
> without needing lazy initialization.
102+
103+
```rust
104+
use std::sync::Mutex;
105+
106+
static GLOBAL_DATA: Mutex<Vec<i32>> = Mutex::new(Vec::new());
107+
```
108+
109+
### How to Run
110+
111+
```bash
112+
cargo run --bin singleton-mutex
113+
```
114+
115+
### Output
116+
117+
```
118+
Called 3 times
119+
```
File renamed without changes.
File renamed without changes.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//! `OnceCell` allows having a custom initialization of a singleton at
2+
//! an arbitrary place. The initialization can be done only once.
3+
//! `Mutex` is still needed to make an actual object modifiable
4+
//! without an `unsafe` block.
5+
6+
use once_cell::sync::OnceCell;
7+
use std::sync::Mutex;
8+
9+
static ARRAY: OnceCell<Mutex<Vec<i32>>> = OnceCell::new();
10+
11+
fn singleton_init(array: Vec<i32>) {
12+
ARRAY.get_or_init(|| Mutex::new(array));
13+
}
14+
15+
fn do_a_call() {
16+
ARRAY.get().unwrap().lock().unwrap().push(1);
17+
}
18+
19+
fn main() {
20+
singleton_init(vec![42]);
21+
22+
do_a_call();
23+
do_a_call();
24+
do_a_call();
25+
26+
println!("{:?}", ARRAY.get().unwrap().lock().unwrap());
27+
}
File renamed without changes.

0 commit comments

Comments
 (0)