Singletons (in Rust)

Singletons (in Rust)

Singletons are generally not easy to implement safely in any programming language, especially in a multithreaded environment. However Rust’s emphasis on (possibly guaranteed) on memory and concurrency safety sprinkled with some additional language constraints makes singletons awkward to implement at best, or plain annoying at worst. There are solutions however. This post will solely focus on the concept of Singletons, in general, and how to implement them in Rust.

This post was written for Rust 1.49. Given the language is, albeit being +10 years old, still evolving significantly the implementations might not stand the test of time for very long although the general concept will remain valid.

Design

This section is language agnostic.

Singleton definition

A singleton (design pattern) is roughly defined as: restricting the instantiation of state to a single instance (for object-oriented languages, an object). The singleton is generally globally accessible throughout the system as opposed to passing around a reference of the single state instance.

The singleton introduces global state in the system, it should NOT be confused with a simple static function. For example a singleton to keeping state related to the available hardware peripherals (think: sensors, GPIO pins and alike for which there are only a predefined number available) in an embedded context. Some people perceive a singleton more as a manager for shared, unique, resources.

Several decisions have to be made during its design which may differ per use-case:

  • Initialization (when/how/where;static/lazy; once/multiple times; configurable or hard-coded)
  • Availability (always available or not, related to initialization)
  • Mutable/immutable singleton state
  • Accessibility/discoverability (globally accessible or not; multithread safe or not)
  • Destruction (are there special requirements, when/how/where)
  • Impact on testing
  • (Prevent multiple instantiations)

Different decisions on the points above will result in different implementations and should be carefully considered.

Technical definition

Technically, low level, a singleton is very easy to implement: having a fixed pointer or reference to the singleton instance in memory suffices. The complexity comes with the chosen programming language and how it can guarantee there is only ever one single instance of the singleton (rogether with the other design decisions). So implementing a singleton is more a “language problem”, hence design pattern1. One will quickly notice when comparing implementations across different languages they can differ quite a bit; however this is out of scope for this post.

Design decisions

First I will have to clarify two terms: singleton state to refer to the implementation of the state itself possibly allowing modification and singleton system referring to the implementation making sure the singleton (state) is accessible and there can only ever be one instance. In some languages (e.g. C#, Java, …) it is possible to create a generic singleton system which can turn any state into a singleton.

Single state versus single reference

The opposition boils down to, can the singleton instance be changed during run-time or not.

If it cannot (single state) the state itself can still be either mutable or immutable. An advantage of this approach, it is safe to store and pass references along e.g. to closures. If the singleton is globally available however, and it usually is, passing along references should not be required. The singleton can simply be accessed directly upon need.

While single reference might be hard to properly implement in most languages (tracking references to the old instance) in rust the borrow checker (and language design) provide excellent guarantees2 which one can rely on. Besides the base implementation some more design questions must be answered though: who can change the instance? when can the instance be changed? are custom implementation allowed (or only a single or predefined types?), should the system be notified when a change occurs? As you can see the design/implementation becomes most often more complex compared to a single state singleton.
If support for storing or passing along references is required, which again if the singleton in globally available should not be the case, one might need to define its own dynamic handle which always points to the current instance of the singleton. Otherwise stored references might still refer to, and use, the old singleton instance, hence introducing multiple active singleton instances which voids the purpose of the Singleton.

One could argue single reference is not a real implementation of the singleton pattern given the singleton state (instance) can be changed, momentarily introducing 2 states. Depending on the use-case this might not be acceptable.

Initialization

Fixed or custom (configurable) singleton state implantation support

Initialization will largely decide how testable the components are within the system. Worst case the Singleton is available on system level which entails if custom singleton state types cannot be specified, all components must always be tested in the context of the full system. While some components could maybe still be mocked, depending on the design, the full system context has to be provided (running) in case a component uses the Singleton. If the Singleton is only available for a subsystem the problem is contained to said subsystem/subcomponent. Depending on the implementation details it might still be possible to test the singleton state implementation separate from the whole system (with unit tests). Note: even when supporting multiple singleton state implementations it does not automatically imply the singleton state implementation can be changed at runtime, e.g. dynamic one-time initialization. For a system wide available Singleton to test/verify the assembly without changing it the singleton state type must be configurable at runtime; whether this is an actual requirement depends on your use-case3.

The most flexible way of supporting custom singleton state implementations is dependency injection which allows one to specify the actual implementation type (dependency) externally hence injecting the dependency (e.g. mock in a test context). There are many ways to implement dependency injection designs, usually differentiating on dependency discovery, ranging from dynamic discovery of new implementation types at run-time4 (e.g. in external assemblies or from a remote server; the latter requiring serious cyber security conciderations).
Implementing dependency injection becomes easier if the application and its initialization is split, e.g. the application is a library which is initialized by a mini application who’s job it is to just initialize the application. This allows for test to initialize the application on their own and providing (or “injecting”) their own implementations. In Rust this is even explicitly supported by specifying a main.rs and lib.rs file in the same (application) project. An additional advantage of this approach, the singleton can remain a one-time initialization (single state) singleton however static initialization is, most likely, no longer possible. One should be cautious to make sure no additional instances of the singleton can be created through the dependency injection framework. While dependency injection is possible in rust (there are several crates available) is generally more popular in (object oriented) languages with (full) reflection support.

Another option is to use a single reference singleton design instead which supports custom singleton state implementations, although the same considerations regarding dependency injection apply.

Supporting custom singleton state implementations will most likely complicate your singleton design significantly; often a fixed singletone state type will do if the system remains testable.

Static versus lazy static initialization

Singleton initialization might be heavy on resources and if never used during execution it is wasteful or even worse impact the program’s performance for its whole lifetime (e.g. memory restricted). Therefore it might be worthwhile to consider only lazy initializing the singleton and defer its actual initialization when used for the first time (possibly introducing a performance impact for the first caller; which might or might not be acceptable depending on the use-case). Another reason why to use lazy initialization if initialization is dependent on other static variables given in most languages the order of static initialization (of none constants) is not guaranteed5 or even supported.

Lazy initialization might require special attention in a multithreaded environment.

The reverse of lazy initialization is sometimes called eager initialization, meaning initialization is always performed. While we discussed it in a static context eager or lazy initialization neither have to be static.

Note: whether or not the singleton in lazily initialized does not imply anything about its availability (more on this later).

Static versus run-time initialization

While static initialization offers the benefits of making the Singleton always available (from the start) it makes run-time dependency injection impossible by design. In addition some languages do not support initialization of non trivial types, e.g. Rust does not allow it.

Run-time initialization offers greater flexibility (dependency injection, configure the singleton with parameters, …) but introduces the problem of making sure the Singleton is properly initialized before it is used and preventing re-initialization if that is not allowed.

When the singleton is initialized will impact the availability of the Singleton, see next section.

Availability

While per definition there must only be a single instance at all times, if any, that implies nothing about its availability. The singleton could only become available late in the system’s initialization or unavailable early in the system destruction depending on the requirements.

Borrow singleton

Based on the principle of optional availability a design to implement a singleton is to make its managed resources borrowable. Hence a borrow singleton, it exposes resources that can be claimed (and never returned for the lifetime of the application) or borrowed (and returned when no longer required); if applicable the singleton state as a whole can be claimed or borrowed. Special attention should be provided during implementation to avoiding borrowed state being lost when going out of scope (destroyed) without being returned to the singleton. Borrowing Singletons are popular in embedded Rust to manage peripherals. For example to manage GPIO pins: once a pin is in use it will generally remain in use and is never returned to the pool of resources (singleton). Using a (borrowing) singleton in this scenario will prevent 2 components using the same GPIO pin simultaneously. When a resource is no longer available and requested it is up to the client on how to handle such scenario, the singleton may provide additional support in the form of spinlocks, mutexes, requests for resources, events, … depending on the requirements. Needless to say a borrowing singleton is required to be mutable. The simplest form of a borrowing singleton is one that only allow borrowing by putting all resources behind a lock, as long as the lock is in use the resource cannot be used by anybody else. Alternatively the state ownership could transferred to the client leaving the field empty (nullpointer or empty optional) in the singleton.

While usually less useful one could also make the singleton as a whole borrowable so it can only be used by one client at the same time6. However usually this is too coarse grained as for example one rarely needs to claim/access all device peripherals, see the previous example, potentially stalling other clients waiting on the singleton to become available to claim other peripherals.

Great care should be taken during implementation on thread safety; if applicable.

Using a borrowing singleton usually requires a bit more state/error handling in the client code. In general they only make sense in the context of access to resources, not for immutable singletons exposing state.

Mutability

Depending on the use-case the singleton state might be mutable or immutable. Immutable is usually easier to implement and per definition thread-safe.

Immutable singletons are often used to expose (fixed) state throughout the application (e.g. global config file parameters) however one should critically ask oneself whether or not a Singleton is the right design for these use-cases. While it could make sense there must indeed exist only one such (config/setting) instance if it must not be globally accessible throughout the system one could consider passing the instance around with a reference instead. Strictly speaking it would still be a singleton, according to the definition, however in practice this is why many people only consider something a Singleton if it is “global accessible” as well.

Mutable singletons are more common, note they might require special attention wrt interior thread-safety see next section.

Accessibility

Generally speaking Singletons are made accessible statically through a static variable or function. While it is possible to implement a Singleton passed along by reference there is practically no use-case for it (none that i have ever encountered or could think of while writing this post). As explained before one could argue it would still be considered a Singleton since many of the design tradeoff we have discussed become obsolete.

For mutable Singletons in a multithreaded environment one should be careful about race conditions, these can be solved by access control to the Singleton (e.g. through a mutex or read/write lock) or have thread safe interior mutability of the singleton state. Which fits best depends on your use case, most often a single mutex is too coarse grained and might require client to wait more than required if not all state must be mutated. However this depend on how often the Singleton is accessed and the number of its managed resources/state.

If the Singleton is globally available it is effectively a “hidden” dependency which is the exact reason why Singletons impact verification/testing.

Destruction

Destruction of the singleton instance is usually related to how it is initialized. One should take care on when the state is no longer available/unusually since depending on the nature of the Singleton other components of the system could still depend on it. To avoid these problems it is no uncommon to destroy the Singleton as last or let the language features take care of it automatically when applicable (e.g. garbage collected languages). As example when one has a Singleton for global configuration data which is persisted to file how are changes to the state handled after the file has been written to persistent storage? Is it ok to silently drop them?

Not all Singletons must be destructed depending on what state/resources they hold.

In Rust drop is NOT called on static items.

- Rust reference book

Implementation

As mentionned before, Rust’s design decisions make it harder and easier at the same time to implement Singletons; compared to other languages. Due to the memory safety and race condition constraints some designs are not possible/harder to implement, however when implemented usually provide better guarantees.

Especially static mut is a problem, it is per definition unsafe (allows multiple aliasing + mutation at the same time, hence possible race conditions which is undefined behavior in Rust) and therefor, albeit possible, discouraged to use. Singletons in Rust are also discussed in the Rust embedded book, especially the borrow singleton design. Here is an example of a logger implemented as Singleton for embedded Rust.

Rust does offer more limited encapsulation features compared to an object oriented programming language like C#. Therefor it might require more attention to make sure the singleton has the proper encapsulation. For example if the stuct is exposed out of a module every one can create a new instance of it. However the normal scoping rules of rust still apply, hence static variables can be added to function bodies and used as structs fields as well. However a full analysis of Rusts encapsulation limitations, and solutions, is out of scope for this post and are largely neglected in the examples.

Note on the implementations: these are just examples which you can/might need to adjust depending on your needs e.g. remove/add Mutex or replace the mutex by a SpinLock ect. This post, in no way shape or form, tries to provide a complete list of implementations.

All example files are available for download at the end of the post.

Implementation examples (in Rust)

Mutable lazy single instance

Unfortunately Rust, currently, only allows primitive types to be initialized statically. While there is an RFC in progress to support lazy static initialization at the moment external crates, or implementing it oneself, is required. I would strongly advice the former e.g. the very popular lazy_static (currently 1.4.0) or one_cell (currently 1.3.1).

When Mutex is removed the singleton becomes immutable. One could consider using a RwLock instead depending on the use-case.

It works quite simple, the static instance is initialized when first accessed. It is protected by a mutex making it safe in a multithreaded environment. That’s it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use lazy_static::lazy_static;
use std::sync::Mutex;
use std::thread;

lazy_static! {
    static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec![]);
}

fn main()
{
    // Single use
    ARRAY.lock().unwrap().push(1);

    // Multi use
    {
        let mut locked_array = ARRAY.lock().unwrap();
        locked_array.push(1);
        locked_array.push(1);
        locked_array.push(1);
    }

    let handle = thread::spawn(move || { ARRAY.lock().unwrap().push(1); });
    handle.join().unwrap();

    assert_eq!(ARRAY.lock().unwrap().len(), 5);
}

When using one_cell instead:

static ARRAY: Lazy<Mutex<Vec<u8>>> = Lazy::new(|| Mutex::new(vec![]));
Pros
  • Threadsafe
  • Single state
  • Lazy static initialization
  • Simple implementation (+not using unsafe)
Cons
  • Not the best ergonomic
  • Requires an external crate
  • Coarse grained locking
  • Drop is not supported

Mutable lazy single instance, nightly

Using nightly features, once_cel, a lazy mutable singleton can be implemented in Rust (with std) without external crates. This implementation is inspired by the stdio implementation in std (one can check the source code yourself).

Implementation wise similar to the previous section, where instead of lazy_static the nightly feature once_cel is used instead. There is some more boilerplate (indirection) to automatically call lock() so the client doe not have to; the downside is that all API calls have to be duplicated. Whether or not this trade of is worth it depends on your use-case.

This implementation well suited for global implementations, e.g. logging, io, …

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#![feature(once_cell)] // Nightly feature, see https://github.com/rust-lang/rust/issues/74465

use std::lazy::SyncOnceCell;
use std::sync::Mutex;
use std::sync::MutexGuard;
use std::thread;

pub struct SingletonState {
    pub value: u8,
}

pub struct Singleton {
    inner: &'static Mutex<SingletonState>,
}

pub fn get() -> Singleton {
    static INSTANCE: SyncOnceCell<Mutex<SingletonState>> = SyncOnceCell::new();
    Singleton {
        inner: INSTANCE.get_or_init(|| Mutex::new(SingletonState { value: 0 })),
    }
}

pub struct SingletonLock<'a> {
    inner: MutexGuard<'a, SingletonState>,
}

// If one wants to support client to lock for multiple calls at once insteaf of per call
impl SingletonLock<'_> {
    pub fn add_value(&mut self, value: u8) {
        self.inner.value += value;
    }
}

impl Singleton {
    pub fn lock(&self) -> SingletonLock<'_> { 
        SingletonLock {
            inner: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
        }
    }

    pub fn get_value(&self) -> u8 {
        self.lock().inner.value
    }

    pub fn add_value(&mut self, value: u8) {
        self.lock().inner.value += value;
    }
}

fn main() {
    let singleton = get();
    {
        let mut singleton_lock = singleton.lock();
        singleton_lock.add_value(1);

        // Do more things with the singleton here
    } // lock is dropped here.

    let handle = thread::spawn(move || {
        let mut singleton = get();
        singleton.add_value(1);
    });
    handle.join().unwrap();

    assert_eq!(2, singleton.get_value());
}
Pros
  • Threafsafe
  • Does not require external crates
  • Mutable singleton state
  • Single state
  • Lazy initialization
Cons
  • Uses a nightly feature
  • Some boilerplate (for better ergonomics)
  • Drop is not supported
  • Coarse grained locking

Mutable lazy, auto descoped, single instance

Just like with the previous lazy singleton this will use an external crate for the initialization. This design is especially useful if no system resource should be used when the Singleton is not used and/or when the Singleton can be re-initialized during the program’s lifetime.

The Singleton instance is lazy initialized upon first need (new() called on MyStruct). Its implementation consists of a static, weak, shared state singleton (MySharedState), a struct to hold the actual singleton state with reference counting (MyStruct) and an struct using the actual singleton state (Resource). All resources get a copy of MyStruct, holding the singleton state, and when the last MyStruct is dropped the singleton is automatically descoped as well. One could see this as an “on need” shared state singleton.

This design is particularly useful for libraries since you do not want to retain more resources as required and have no control over how the library is used. It can also be used as borrowing singleton for resources, e.g. GPIO pins.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use lazy_static::lazy_static;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
use std::thread;

// Initialize an array with a type that doesn't implement Copy
macro_rules! init_array {
// Shared stated which is dropped after all instances using the state go out of scope.
($val:expr, $len:expr) => {{
    // Based on https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element

    use std::mem::{self, MaybeUninit};

        // Create an uninitialized array of `MaybeUninit`. The `assume_init` is
        // safe because the type we are claiming to have initialized here is a
        // bunch of `MaybeUninit`s, which do not require initialization.
        let mut data: [MaybeUninit<_>; $len] = unsafe { MaybeUninit::uninit().assume_init() };
        // Dropping a `MaybeUninit` does nothing. Thus using raw pointer
        // assignment instead of `ptr::write` does not cause the old
        // uninitialized value to be dropped. Also if there is a panic during
        // this loop, we have a memory leak, but there is no memory safety
        // issue.
        for elem in &mut data[..] {
            *elem = MaybeUninit::new($val);
        }

        // Everything is initialized. Transmute the array to the
        // initialized type.
        unsafe { mem::transmute::<_, [_; $len]>(data) }
    }};
}

// Shared stated which is dropped after all instances using the state go out of scope.
lazy_static! {
    static ref MY_STATE: Mutex<Weak<MySharedState>> = Mutex::new(Weak::new());
}

const MAX: u8 = 150;

#[derive(Debug)]
pub struct MySharedState {
    resources_taken: [AtomicBool; MAX as usize],
}

// Provides access to the shared state and "owns" it.
#[derive(Clone, Debug)]
pub struct MyStruct {
    inner: Arc<MySharedState>,
}

// Acts as a handle for the shared state, e.g. a GPIO pin
pub struct Resource {
    state: u8,
    inner: Arc<MySharedState>,
}

impl MyStruct {
    pub fn new() -> Result<MyStruct, String> {
        let mut static_state = MY_STATE.lock().unwrap();
    
    // Clone a strong reference if a MySharedState instance already exists, otherwise
    // initialize it here so we can return any relevant errors.
    if let Some(ref state) = static_state.upgrade() {
        Ok(MyStruct {
            inner: state.clone(),
        })
    } else {
        // Init MySharedState first
        let resources_taken = init_array!(AtomicBool::new(false), MAX as usize);

        let shared_state = Arc::new(MySharedState { resources_taken });

            // Store a weak reference to our state. This gets dropped when
            // all MyStruct and Resource instances go out of scope.
            *static_state = Arc::downgrade(&shared_state);

            Ok(MyStruct {
                inner: shared_state,
            })
        }
    }

    pub fn get(&self, nr: u8) -> Result<Resource, String> {
        // Returns true if the resource is already taken, otherwise atomically sets it to true here
        if self.inner.resources_taken[nr as usize].compare_and_swap(false, true, Ordering::SeqCst) {
            Err("Already in use".to_string())
        } else {
            Ok(Resource {
                state: nr,
                inner: self.inner.clone(),
            })
        }
    }
}

fn main() {
    {
        let scoped_singleton = MyStruct::new().unwrap(); // This could be part of a struct or other singleton
        let resource1 = scoped_singleton.get(1); // Get resource1

        // Do something

        let handle = thread::spawn(move || {
            let resource2 = scoped_singleton.get(2);
            
            // Do something
        });
        handle.join().unwrap();

        // Do something
    }

    // Now the shared singleton state does not exists anymore
}
Pros
  • Threadsafe
  • Auto descoped when not needed
  • Single state
  • Lazy initialization
  • Can implement borrowing singleton
  • Drop will automatically be called, if implemented
  • Supports fine-grained locking
Cons
  • Requires an external crate
  • Requires a bit of boilerplate
  • Uses unsafe

Single, primitive, value states

For the special use-case where the global state only consists of (a few), trivial-typed, fields one can use atomics.

The values are mutable, thread-safe and statically initialized.

1
2
3
4
5
6
7
use std::sync::atomic::{AtomicI32, Ordering};

static VALUE1: AtomicI32 = AtomicI32::new(0);

fn main() {
    VALUE1.fetch_add(1, Ordering::Relaxed);
}
Pros
  • Static initialization
  • Simple implementation (+not using unsafe)
  • No external crates
  • Fine grained locking & control
Cons
  • Ergonomically rather verbose
  • Only for trivial types

Thread local singleton

This implementation is based on Sentry’s blogpost by Armin Ronacher you can ready his excellent blogpost instead.

This singleton design is a default constructed globally available single reference, thread safe, immutable singleton which ensures local consistency. The latter means the singleton state is read-only but can be changed to another instance, however once a reference to a particular state is obtained said state is guaranteed to never change. However the current global singleton state may be updated in the meanwhile. This entails there can be multiple singleton states active at the same time, technically violating the Singleton definition.

So how does this work, the clue is using thread_local storage and thread-safe (atomic) immutable reference counting memory management (Arc). Client are given a new read-only reference (see Clone() on line 10) to the current instance which will bind it to the current thread (local storage) and make sure said instance will exists as long as the reference is not dropped. By saving the instance in thread local storage it will live as long as the thread is alive giving every thread its own singleton instance (copy).
To make sure changing the config is safe the (global) singleton reference is guarded with a read-write (RwLock) lock.

Note even though the debug_mode field is public (and thus mutable) due to the Config being wrapped in an Arc, which makes it immutable, the value cannot be changed saving us making the field private and writing a getter for it.

One may switch Arc for Rc and RwLock for RefCell if in a single threaded environment. If you are just working with thread locals you can also combine RefCell with Arc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use std::sync::{Arc, RwLock};

#[derive(Default)]
struct Config {
    pub debug_mode: bool,
}

impl Config {
    pub fn current() -> Arc<Config> {
        CURRENT_CONFIG.with(|c| c.read().unwrap().clone())
    }
    pub fn make_current(self) {
        CURRENT_CONFIG.with(|c| *c.write().unwrap() = Arc::new(self))
    }
}

thread_local! {
    static CURRENT_CONFIG: RwLock<Arc<Config>> = RwLock::new(Default::default());
}

fn main() {
    Config { debug_mode: true }.make_current();
    if Config::current().debug_mode {
        // do something
    }
}

To recap: instead of using interior mutability where an object changes its internal state, consider using a pattern where you promote new state to be current and current consumers of the old state will continue to hold on to it by putting an Arc into an RwLock.

- by Armin Ronacher

Pros
  • Default eager static initialization
  • (Thread) Local consistency
  • Simple implementation (+not using unsafe)
  • Good ergonomy
  • No locking after acquiring the handle
  • Mutable singleton state
  • No external crates
Cons
  • Not a real singleton
  • Only for immutable data

  1. design patterns are designs which yield to certain set of properties which are commonly required. They are often tied to languages, for example in language A you want to have an implementation with a property Y for which you can implement a particular design pattern (or come up with your own implementation/design) but in another language B, Y might come for free or is not applicable so does not require a design pattern. Some patterns are rather universal, like singleton, albeit can differ significantly in implementation between programming languages. Also within a design pattern implementation there are different flavours as demonstrated in this pose. ↩︎

  2. the compiler (barring compiler bugs) statically guarantees references always point to valid objects. More so, without using unsafe, one cannot store references without life time guarantees or being from a references counted wrapper. ↩︎

  3. often is regulated markets like medical devices the deliverable, assembly, must be verified as how it is delivered so separate builds for production and testing is not allowed by default and has to be justified. ↩︎

  4. assuming compile time or link time dependency injection/code generation is not feasible e.g. like svd2rust provides for hardware features. ↩︎

  5. commonly dubbed the “Static Initialization Order Fiasco”↩︎

  6. often, in more naive implementations, this is implemented by accident as a side effect of protecting the whole singleton behind a mutex for thread safety. While effective it might not be the most efficient. ↩︎

Noticed an error in this post? Corrections are appreciated.

© Nelis Oostens