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.
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.
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
, which returns Option<&T>
.
let x = MaybeBox::<String>::default();
if some_runtime_config {
x.lazy_init(String::from("hello"));
}
if let Some(val) = x.get() {
println!("{}", val);
}
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();
Option<T>
Late initialization requires mutable access to the Option
. This is fine
unless the parent struct is already shared.
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
.
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.
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.
FreezeBox is compatible with no_std
projects (no feature flags needed).
It may be used in any environment with a memory allocator.
FreezeBox uses unsafe code internally. To ensure soundness, the unit tests pass under Miri, and the unsafe code is simple and easy to understand.
The minimum supported Rust version is 1.48.