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> {}