Vadzim Dambrouski 4 years ago
commit
88c1a5101e
3 changed files with 286 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 9 0
      Cargo.toml
  3. 275 0
      src/lib.rs

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+/target
+Cargo.lock

+ 9 - 0
Cargo.toml

@@ -0,0 +1,9 @@
+[package]
+name = "nostd_cell"
+version = "0.1.0"
+authors = ["Vadzim Dambrouski <pftbest@gmail.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]

+ 275 - 0
src/lib.rs

@@ -0,0 +1,275 @@
+//! The code is based on https://github.com/matklad/once_cell
+
+#![no_std]
+
+use core::{
+    cell::UnsafeCell,
+    fmt,
+    mem::MaybeUninit,
+    ptr,
+    sync::atomic::{AtomicU8, Ordering},
+};
+
+// Three states that a OnceCell can be in, encoded into the lower bits of `state` in
+// the OnceCell structure.
+const INCOMPLETE: u8 = 0x0;
+const RUNNING: u8 = 0x1;
+const COMPLETE: u8 = 0x2;
+
+/// A cell which can be written to only once.
+///
+/// Unlike `:td::cell::RefCell`, a `OnceCell` provides simple `&`
+/// references to the contents.
+///
+/// # Example
+/// ```
+/// use nostd_cell::OnceCell;
+///
+/// let cell = OnceCell::new();
+/// assert!(cell.get().is_none());
+///
+/// let value: &String = cell.get_or_init(|| {
+///     "Hello, World!".to_string()
+/// });
+/// assert_eq!(value, "Hello, World!");
+/// assert!(cell.get().is_some());
+/// ```
+pub struct OnceCell<T> {
+    inner: UnsafeCell<MaybeUninit<T>>,
+    state: AtomicU8,
+}
+
+impl<T> Default for OnceCell<T> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<T: fmt::Debug> fmt::Debug for OnceCell<T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self.get() {
+            Some(v) => f.debug_tuple("OnceCell").field(v).finish(),
+            None => f.write_str("OnceCell(Uninit)"),
+        }
+    }
+}
+
+impl<T: Clone> Clone for OnceCell<T> {
+    fn clone(&self) -> OnceCell<T> {
+        let res = OnceCell::new();
+        if let Some(value) = self.get() {
+            match res.set(value.clone()) {
+                Ok(()) => (),
+                Err(_) => unreachable!(),
+            }
+        }
+        res
+    }
+}
+
+impl<T: PartialEq> PartialEq for OnceCell<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.get() == other.get()
+    }
+}
+
+impl<T: Eq> Eq for OnceCell<T> {}
+
+impl<T> From<T> for OnceCell<T> {
+    fn from(value: T) -> Self {
+        OnceCell {
+            inner: UnsafeCell::new(MaybeUninit::new(value)),
+            state: AtomicU8::new(COMPLETE),
+        }
+    }
+}
+
+impl<T> OnceCell<T> {
+    /// Creates a new empty cell.
+    pub const fn new() -> OnceCell<T> {
+        OnceCell {
+            inner: UnsafeCell::new(MaybeUninit::uninit()),
+            state: AtomicU8::new(INCOMPLETE),
+        }
+    }
+
+    /// Gets the reference to the underlying value.
+    ///
+    /// Returns `None` if the cell is empty.
+    pub fn get(&self) -> Option<&T> {
+        if self.is_complete_acquire() {
+            Some(self.get_unsafe())
+        } else {
+            None
+        }
+    }
+
+    /// Gets the mutable reference to the underlying value.
+    ///
+    /// Returns `None` if the cell is empty.
+    pub fn get_mut(&mut self) -> Option<&mut T> {
+        if self.is_complete_acquire() {
+            Some(unsafe { &mut *(*self.inner.get()).as_mut_ptr() })
+        } else {
+            None
+        }
+    }
+
+    fn is_complete_acquire(&self) -> bool {
+        self.state.load(Ordering::Acquire) == COMPLETE
+    }
+
+    fn get_unsafe(&self) -> &T {
+        unsafe { &*(*self.inner.get()).as_ptr() }
+    }
+
+    fn set_unsafe(&self, value: T) {
+        unsafe {
+            ptr::write((*self.inner.get()).as_mut_ptr(), value);
+            self.state.store(COMPLETE, Ordering::Release);
+        }
+    }
+
+    /// Sets the contents of this cell to `value`.
+    ///
+    /// Returns `Ok(())` if the cell was empty and `Err(value)` if it was
+    /// full.
+    ///
+    /// # Example
+    /// ```
+    /// use nostd_cell::OnceCell;
+    ///
+    /// let cell = OnceCell::new();
+    /// assert!(cell.get().is_none());
+    ///
+    /// assert_eq!(cell.set(92), Ok(()));
+    /// assert_eq!(cell.set(62), Err(62));
+    ///
+    /// assert!(cell.get().is_some());
+    /// ```
+    pub fn set(&self, value: T) -> Result<(), T> {
+        let state = self
+            .state
+            .compare_and_swap(INCOMPLETE, RUNNING, Ordering::AcqRel);
+        if state == INCOMPLETE {
+            self.set_unsafe(value);
+        } else {
+            return Err(value);
+        }
+        Ok(())
+    }
+
+    /// Gets the contents of the cell, initializing it with `f`
+    /// if the cell was empty.
+    ///
+    /// # Panics
+    ///
+    /// If `f` panics, the panic is propagated to the caller, and the cell
+    /// remains uninitialized.
+    ///
+    /// It is an error to reentrantly initialize the cell from `f`. Doing
+    /// so results in a panic.
+    ///
+    /// # Example
+    /// ```
+    /// use nostd_cell::OnceCell;
+    ///
+    /// let cell = OnceCell::new();
+    /// let value = cell.get_or_init(|| 92);
+    /// assert_eq!(value, &92);
+    /// let value = cell.get_or_init(|| unreachable!());
+    /// assert_eq!(value, &92);
+    /// ```
+    pub fn get_or_init<F>(&self, f: F) -> &T
+    where
+        F: FnOnce() -> T,
+    {
+        enum Void {}
+        match self.get_or_try_init(|| Ok::<T, Void>(f())) {
+            Ok(val) => val,
+            Err(void) => match void {},
+        }
+    }
+
+    /// Gets the contents of the cell, initializing it with `f` if
+    /// the cell was empty. If the cell was empty and `f` failed, an
+    /// error is returned.
+    ///
+    /// # Panics
+    ///
+    /// If `f` panics, the panic is propagated to the caller, and the cell
+    /// remains uninitialized.
+    ///
+    /// It is an error to reentrantly initialize the cell from `f`. Doing
+    /// so results in a panic.
+    ///
+    /// # Example
+    /// ```
+    /// use nostd_cell::OnceCell;
+    ///
+    /// let cell = OnceCell::new();
+    /// assert_eq!(cell.get_or_try_init(|| Err(())), Err(()));
+    /// assert!(cell.get().is_none());
+    /// let value = cell.get_or_try_init(|| -> Result<i32, ()> {
+    ///     Ok(92)
+    /// });
+    /// assert_eq!(value, Ok(&92));
+    /// assert_eq!(cell.get(), Some(&92))
+    /// ```
+    pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
+    where
+        F: FnOnce() -> Result<T, E>,
+    {
+        let state = self
+            .state
+            .compare_and_swap(INCOMPLETE, RUNNING, Ordering::AcqRel);
+        match state {
+            COMPLETE => {
+                return Ok(self.get_unsafe());
+            }
+            INCOMPLETE => {
+                // In case f() panics, OnceCell will be permanently empty (in RUNNING state).
+                match f() {
+                    Ok(value) => {
+                        self.set_unsafe(value);
+                        return Ok(self.get_unsafe());
+                    }
+                    Err(err) => {
+                        self.state.store(INCOMPLETE, Ordering::Release);
+                        return Err(err);
+                    }
+                }
+            }
+            _ => {
+                // It would be great to return error here, but API doesn't allow it.
+                panic!("reentrant init");
+            }
+        }
+    }
+
+    /// Consumes the `OnceCell`, returning the wrapped value.
+    ///
+    /// Returns `None` if the cell was empty.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use nostd_cell::OnceCell;
+    ///
+    /// let cell: OnceCell<String> = OnceCell::new();
+    /// assert_eq!(cell.into_inner(), None);
+    ///
+    /// let cell = OnceCell::new();
+    /// cell.set("hello".to_string()).unwrap();
+    /// assert_eq!(cell.into_inner(), Some("hello".to_string()));
+    /// ```
+    pub fn into_inner(self) -> Option<T> {
+        // Because `into_inner` takes `self` by value, the compiler statically verifies
+        // that it is not currently borrowed. So it is safe to move out `Option<T>`.
+        if self.is_complete_acquire() {
+            Some(unsafe { self.inner.into_inner().assume_init() })
+        } else {
+            None
+        }
+    }
+}