diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7151095..d1efbb0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index fed9697..af72350 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/README.md b/README.md index 5179f44..61a2be0 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/creational/singleton/README.md b/creational/singleton/README.md index f30c52d..ddde16d 100644 --- a/creational/singleton/README.md +++ b/creational/singleton/README.md @@ -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> = 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. diff --git a/creational/singleton/Cargo.toml b/creational/singleton/how-to-create/Cargo.toml similarity index 71% rename from creational/singleton/Cargo.toml rename to creational/singleton/how-to-create/Cargo.toml index 0f2c598..fdadc8f 100644 --- a/creational/singleton/Cargo.toml +++ b/creational/singleton/how-to-create/Cargo.toml @@ -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]] @@ -17,3 +18,7 @@ path = "lazy.rs" [[bin]] name = "singleton-mutex" path = "mutex.rs" + +[[bin]] +name = "singleton-once" +path = "once.rs" diff --git a/creational/singleton/how-to-create/README.md b/creational/singleton/how-to-create/README.md new file mode 100644 index 0000000..cca4d67 --- /dev/null +++ b/creational/singleton/how-to-create/README.md @@ -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> = Mutex::new(Vec::new()); +``` + +### How to Run + +```bash +cargo run --bin singleton-mutex +``` + +### Output + +``` +Called 3 times +``` diff --git a/creational/singleton/lazy.rs b/creational/singleton/how-to-create/lazy.rs similarity index 100% rename from creational/singleton/lazy.rs rename to creational/singleton/how-to-create/lazy.rs diff --git a/creational/singleton/mutex.rs b/creational/singleton/how-to-create/mutex.rs similarity index 100% rename from creational/singleton/mutex.rs rename to creational/singleton/how-to-create/mutex.rs diff --git a/creational/singleton/how-to-create/once.rs b/creational/singleton/how-to-create/once.rs new file mode 100644 index 0000000..7b03335 --- /dev/null +++ b/creational/singleton/how-to-create/once.rs @@ -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>> = OnceCell::new(); + +fn singleton_init(array: Vec) { + 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()); +} diff --git a/creational/singleton/safe.rs b/creational/singleton/how-to-create/safe.rs similarity index 100% rename from creational/singleton/safe.rs rename to creational/singleton/how-to-create/safe.rs diff --git a/creational/singleton/logger/Cargo.toml b/creational/singleton/logger/Cargo.toml new file mode 100644 index 0000000..aa4f4f2 --- /dev/null +++ b/creational/singleton/logger/Cargo.toml @@ -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" \ No newline at end of file diff --git a/creational/singleton/logger/README.md b/creational/singleton/logger/README.md new file mode 100644 index 0000000..e34ace0 --- /dev/null +++ b/creational/singleton/logger/README.md @@ -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 +``` diff --git a/creational/singleton/logger/app.rs b/creational/singleton/logger/app.rs new file mode 100644 index 0000000..dec7c33 --- /dev/null +++ b/creational/singleton/logger/app.rs @@ -0,0 +1,7 @@ +use crate::log::{error, info, warn}; + +pub fn run() { + info("Application starts"); + warn("Something non-critical happened"); + error("Execution failed") +} diff --git a/creational/singleton/logger/log.rs b/creational/singleton/logger/log.rs new file mode 100644 index 0000000..7fec8b7 --- /dev/null +++ b/creational/singleton/logger/log.rs @@ -0,0 +1,62 @@ +use once_cell::sync::OnceCell; +use std::fmt::Display; + +// OnceCell allows to set a logger later but only once. +static LOGGER: OnceCell> = OnceCell::new(); + +#[derive(Debug)] +pub struct SetLoggerError; + +pub fn set_boxed_logger(logger: Box) -> Result<(), SetLoggerError> { + if LOGGER.set(logger).is_err() { + return Err(SetLoggerError); + } + + Ok(()) +} + +#[derive(PartialEq, Eq, PartialOrd)] +pub enum Level { + Error, + Warn, + Info, +} + +pub trait Log { + fn enabled(&self, level: &Level) -> bool; + fn log(&self, level: &Level, message: &str); +} + +fn log(level: Level, message: &str) { + if let Some(logger) = LOGGER.get() { + if logger.enabled(&level) { + logger.log(&level, message); + } + } +} + +pub fn error(message: &str) { + log(Level::Error, message); +} + +pub fn warn(message: &str) { + log(Level::Warn, message); +} + +pub fn info(message: &str) { + log(Level::Info, message); +} + +impl Display for Level { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Level::Error => "E", + Level::Warn => "W", + Level::Info => "I", + } + ) + } +} diff --git a/creational/singleton/logger/main.rs b/creational/singleton/logger/main.rs new file mode 100644 index 0000000..17b5178 --- /dev/null +++ b/creational/singleton/logger/main.rs @@ -0,0 +1,13 @@ +mod app; +mod log; +mod simple_logger; + +use log::{info, Level}; + +fn main() { + info("This log is not going to be displayed"); + + simple_logger::init(Level::Info); + + app::run(); +} diff --git a/creational/singleton/logger/simple_logger.rs b/creational/singleton/logger/simple_logger.rs new file mode 100644 index 0000000..57e0492 --- /dev/null +++ b/creational/singleton/logger/simple_logger.rs @@ -0,0 +1,20 @@ +use crate::log::{self, Level, Log}; + +struct SimpleLogger { + max_level: Level, +} + +impl Log for SimpleLogger { + fn enabled(&self, level: &Level) -> bool { + *level <= self.max_level + } + + fn log(&self, level: &Level, message: &str) { + println!("[{}] {}", level, message); + } +} + +pub fn init(max_level: Level) { + log::set_boxed_logger(Box::new(SimpleLogger { max_level })) + .expect("Logger has been already set"); +}