toy_async_runtime/lazy.rs
1//! Completely stolen from https://github.com/mgattozzi/whorl/blob/5e61714acf7d29e2b97cd26f6f0587060fa652cf/src/lib.rs#L306
2use std::cell::UnsafeCell;
3use std::mem::MaybeUninit;
4use std::sync::Once;
5
6/// We want to have a static value that's set at runtime and this executor will
7/// only use libstd. As of 10/26/21, the lazy types in std are still only on
8/// nightly and we can't use another crate, so crates like `once_cell` and
9/// `lazy_static` are also out. Thus, we create our own Lazy type so that it will
10/// calculate the value only once and only when we need it.
11pub struct Lazy<T> {
12 /// `Once` is a neat synchronization primitive that we just talked about
13 /// and this is where we need it! We want to make sure we only write into
14 /// the value of the Lazy type once and only once. Otherwise we'd have some
15 /// really bad things happen if we let static values be mutated. It'd break
16 /// thread safety!
17 once: Once,
18 /// The cell is where we hold our data. The use of `UnsafeCell` is what lets
19 /// us sidestep Rust's guarantees, provided we actually use it correctly and
20 /// still uphold those guarantees. Rust can't always validate that
21 /// everything is safe, even if it is, and so the flexibility it provides
22 /// with certain library types and unsafe code lets us handle those cases
23 /// where the compiler cannot possibly understand it's okay. We also use the
24 /// `MaybeUninit` type here to avoid undefined behavior with uninitialized
25 /// data. We'll need to drop the inner value ourselves though to avoid
26 /// memory leaks because data may not be initialized and so the type won't
27 /// call drop when it's not needed anymore. We could get away with not doing
28 /// it though since we're only using it for static values, but let's be
29 /// thorough here!
30 cell: UnsafeCell<MaybeUninit<T>>,
31}
32
33impl<T> Lazy<T> {
34 /// We must construct the type using a const fn so that it can be used in
35 /// `static` contexts. The nice thing is that all of the function calls we
36 /// make here are also const and so this will just work. The compiler will
37 /// figure it all out and make sure the `Lazy` static value exists in our
38 /// final binary.
39 pub const fn new() -> Self {
40 Self {
41 once: Once::new(),
42 cell: UnsafeCell::new(MaybeUninit::uninit()),
43 }
44 }
45 /// We want a way to check if we have initialized the value so that we can
46 /// get the value from cell without causing who knows what kind of bad
47 /// things if we read garbage data.
48 fn is_initialized(&self) -> bool {
49 self.once.is_completed()
50 }
51
52 /// This function will either grab a reference to the type or creates it
53 /// with a given function
54 pub fn get_or_init(&self, func: fn() -> T) -> &T {
55 self.once.call_once(|| {
56 // /!\ SAFETY /!\: We only ever write to the cell once
57 //
58 // We first get a `*mut MaybeUninit` to the cell and turn it into a
59 // `&mut MaybeUninit`. That's when we call `write` on `MaybeUninit`
60 // to pass the value of the function into the now initialized
61 // `MaybeUninit`.
62 (unsafe { &mut *self.cell.get() }).write(func());
63 });
64 // /!\ SAFETY /!\: We already made sure `Lazy` was initialized with our call to
65 // `call_once` above
66 //
67 // We now want to actually retrieve the value we wrote so that we can
68 // use it! We get the `*mut MaybeUninit` from the cell and turn it into
69 // a `&MaybeUninit` which then lets us call `assume_init_ref` to get
70 // the `&T`. This function - much like `get` - is also unsafe, but since we
71 // know that the value is initialized it's fine to call this!
72 unsafe { &(*self.cell.get()).assume_init_ref() }
73 }
74}
75
76/// We now need to implement `Drop` by hand specifically because `MaybeUninit`
77/// will need us to drop the value it holds by ourselves only if it exists. We
78/// check if the value exists, swap it out with an uninitialized value and then
79/// change `MaybeUninit<T>` into just a `T` with a call to `assume_init` and
80/// then call `drop` on `T` itself
81impl<T> Drop for Lazy<T> {
82 fn drop(&mut self) {
83 if self.is_initialized() {
84 let old = std::mem::replace(unsafe { &mut *self.cell.get() }, MaybeUninit::uninit());
85 drop(unsafe { old.assume_init() });
86 }
87 }
88}
89
90/// Now you might be asking yourself why we are implementing these traits by
91/// hand and also why it's unsafe to do so. `UnsafeCell`is the big reason here
92/// and you can see this by commenting these two lines and trying to compile the
93/// code. Because of how auto traits work then if any part is not `Send` and
94/// `Sync` then we can't use `Lazy` for a static. Note that auto traits are a
95/// compiler specific thing where if everything in a type implements a trait
96/// then that type also implements it. `Send` and `Sync` are great examples of
97/// this where any type becomes `Send` and/or `Sync` if all its types implement
98/// them too! `UnsafeCell` specifically implements !Sync and since it is not
99/// `Sync` then it can't be used in a `static`. We can override this behavior
100/// though by implementing these traits for `Lazy` here though. We're saying
101/// that this is okay and that we uphold the invariants to be `Send + Sync`. We
102/// restrict it though and say that this is only the case if the type `T`
103/// *inside* `Lazy` is `Sync` only if `T` is `Send + Sync`. We know then that
104/// this is okay because the type in `UnsafeCell` can be safely referenced
105/// through an `&'static` and that the type it holds is also safe to use across
106/// threads. This means we can set `Lazy` as `Send + Sync` even though the
107/// internal `UnsafeCell` is !Sync in a safe way since we upheld the invariants
108/// for these traits.
109unsafe impl<T: Send> Send for Lazy<T> {}
110unsafe impl<T: Send + Sync> Sync for Lazy<T> {}