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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! # FreezeBox: atomic lazy-initialized ref-able containers.
//!
//! This crate contains two similar container types: [`FreezeBox`] and
//! [`MaybeBox`]. Both containers can be late-initialized using only a
//! shared reference, and both allow a caller to get a reference to the
//! value inside.
//!
//! ```
//! # use freezebox::FreezeBox;
//! let x = FreezeBox::<String>::default();
//! x.lazy_init(String::from("hello"));
//! assert_eq!(x.len(), 5);
//! ```
//!
//! This is useful for data structures that are shared first, but some
//! members of that data structure gets initialized later.
//!
//! ```
//! # use freezebox::FreezeBox;
//! # use std::sync::Arc;
//! let x = FreezeBox::<String>::default();
//! let shared_x = Arc::new(x);
//! shared_x.lazy_init(String::from("hello"));
//! assert_eq!(shared_x.len(), 5);
//! ```
//!
//! [`FreezeBox`] and [`MaybeBox`] share some behavior: they can only be
//! initialized once; initialization is atomic; and the initialized value
//! may never be removed, except by consuming the container with
//! `into_inner()`.
//!
//! The main difference between `FreezeBox` and `MaybeBox` is that
//! `FreezeBox` implements `Deref`. `FreezeBox` is intended to be used in situations
//! where the inner value is always expected to be present before an attempted
//! use. In this scenario, trying to read an uninitialized `FreezeBox` is a
//! bug, so the attempted read will cause a panic.
//!
//! `MaybeBox` is meant to be used in a situation where the inner value may
//! sometimes be missing. `MaybeBox` does not implement `Deref`; instead we
//! need to call [`get`][`MaybeBox::get`], which returns `Option<&T>`.
//!
//! ```
//! # use freezebox::MaybeBox;
//! # let some_runtime_config = true;
//! let x = MaybeBox::<String>::default();
//! if some_runtime_config {
//!     x.lazy_init(String::from("hello"));
//! }
//! if let Some(val) = x.get() {
//!     println!("{}", val);
//! }
//! ```
//!
//! # Examples
//!
//! This example creates a shared data structure, then initializes a member
//! variable later.
//!
//! ```
//! use freezebox::FreezeBox;
//! use std::sync::Arc;
//!
//! /// A data structure that we will initialize late.
//! #[derive(Default)]
//! struct Resources {
//!     name: FreezeBox<String>
//! }
//!
//! // Create an instance of the `Resources` struct, which contains an
//! // uninitialized `name` field.
//! let resources = Arc::new(Resources::default());
//!
//! // Clone the Arc to emulate sharing with other threads, contexts,
//! // or data structures.
//! let res2 = resources.clone();
//!
//! // Here we emulate another thread accessing the shared data structure.
//! // NOTE: it's still our responsibility to ensure that the FreezeBox
//! // is initialized before anyone dereferences it.
//! //
//! let func = move || {
//!     // explicit deref
//!     assert_eq!(*res2.name, "Hello!");
//!     // implicit deref allows transparent access to inner methods
//!     assert_eq!(res2.name.len(), 6);
//! };
//!
//! resources.name.lazy_init("Hello!".to_string());
//! func();
//! ```
//!
//! ## Comparison to other approaches
//!
//! 1. `Option<T>`
//!
//! Late initialization requires mutable access to the `Option`. This is fine
//! unless the parent struct is already shared.
//!
//! 2. `Mutex<Option<T>>`
//!
//! This solves the problem of late-initialization, but requires every caller
//! to lock the `Mutex` and unwrap the `Option`. The added code and runtime
//! overhead might not be desirable, paticularly if all we need is a shared
//! reference to the inner `T`.
//!
//! 3. [`lazy_static`]
//!
//! `lazy_static!` declares a hidden `static` variable, so it's not suitable
//! for lazy-initialized struct members. It also requires the initialization
//! code to be placed at the point of declaration, and uses a spinlock
//! internally.
//!
//! 4. [`once_cell`]
//!
//! `once_cell` is generally preferable to `lazy_static` in new Rust code, and
//! would be a good choice in the case where multiple threads are racing to
//! initialize the inner value.
//!
//! `OnceCell` doesn't implement `Deref`, and requires explicit calls to
//! `get()` or `get_or_init()`. This is similar to `MaybeBox`, but is more
//! verbose in the use case `FreezeBox` was designed for, where readers expect
//! the value to be already initialized.
//!
//! `OnceCell` does not `Box` the internal value, but this makes the atomic
//! initialization more complicated, so `once_cell::sync::OnceCell` is not
//! available in `no_std` contexts.
//!
//! [`lazy_static`]: https://docs.rs/lazy_static
//! [`once_cell`]: https://docs.rs/once_cell

#![no_std]

extern crate alloc;

mod freezebox;
mod maybebox;

pub use self::freezebox::FreezeBox;
pub use self::maybebox::MaybeBox;