diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c12748 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +.idea/ +*.iml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..04acc3b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4 @@ +[[package]] +name = "slot" +version = "0.1.0" + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c048909 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "slot" +version = "0.1.0" +authors = ["Ondřej Hruška "] +edition = "2018" + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9a57dd8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,254 @@ +use std::cell::{Ref, RefCell}; +use std::fmt; +use std::rc::Rc; + +/// Smart cell that wraps an optional reference-counted value, +/// which can be leased, temporarily borrowed, removed, or replaced. +pub struct Slot { + v: RefCell>>, +} + +impl Slot { + /// Create a slot + /// + /// # Examples + /// + /// ```rust + /// use slot::Slot; + /// let s : Slot = Slot::new(); + /// assert_eq!(true, s.is_empty()); + /// ``` + pub fn new() -> Slot { + Slot { + v: RefCell::new(None), + } + } + + /// Create a slot with an initial value + /// + /// # Examples + /// + /// ``` + /// use slot::Slot; + /// let x = String::from("lorem"); + /// let s : Slot = Slot::new_with(x); + /// + /// assert_eq!("lorem", s.lease().as_str()); + /// assert_eq!("lorem", s.take().as_str()); + /// ``` + pub fn new_with(value: T) -> Slot { + Slot { + v: RefCell::new(Some(Rc::new(value))), + } + } + + /// Take a hold of the inner value via an `Rc`. + /// + /// # Panics + /// + /// Panics if empty + /// + /// # Examples + /// + /// ``` + /// use slot::Slot; + /// let s : Slot = Slot::new_with(String::from("lorem")); + /// + /// let lease1 = s.lease(); + /// let lease2 = s.lease(); + /// + /// assert_eq!("lorem", lease1.as_str()); + /// assert_eq!("lorem", lease2.as_str()); + /// + /// s.put(String::from("ipsum")); // here we replace the value, but the old Rc's remain valid + /// + /// assert_eq!("lorem", lease1.as_str()); + /// assert_eq!("lorem", lease2.as_str()); + /// + /// let lease3 = s.lease(); + /// assert_eq!("ipsum", lease3.as_str()); + /// ``` + pub fn lease(&self) -> Rc { + Rc::clone(self.v.borrow().as_ref().unwrap()) + } + + /// Check if the container is empty + /// + /// # Examples + /// + /// ``` + /// use slot::Slot; + /// let s : Slot = Slot::new(); + /// assert!(s.is_empty()); + /// + /// let s = Slot::new_with(132); + /// assert!(! s.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.v.borrow().is_none() + } + + /// Put a value into the slot. Returns the old value, if any. + /// + /// # Panics + /// + /// Panics if the value is currently borrowed. + /// + /// # Examples + /// + /// ``` + /// use slot::Slot; + /// use std::rc::Rc; + /// + /// let s : Slot = Slot::new(); + /// s.put(123); + /// + /// assert!(! s.is_empty()); + /// assert_eq!(123, Rc::try_unwrap(s.take()).unwrap()); + /// ``` + pub fn put(&self, value: T) -> Option> { + self.v.replace(Some(Rc::new(value))) + } + + /// Take a value out of the slot, setting it to `None`. + /// If there are any leases, they remain valid, as they point + /// to clones of the same `Rc`. + /// + /// # Panics + /// + /// Panics if empty + /// + /// # Examples + /// + /// ``` + /// use slot::Slot; + /// use std::rc::Rc; + /// + /// let s : Slot = Slot::new(); + /// s.put(789); + /// + /// assert!(! s.is_empty()); + /// let r = s.take(); + /// assert!(s.is_empty()); + /// + /// assert_eq!(789, Rc::try_unwrap(r).unwrap()); + /// ``` + pub fn take(&self) -> Rc { + let x = self.v.replace(None); + x.unwrap() + } + + /// Empty the slot. This is equivalent to `take()`, except the old value is discarded, if any. + /// + /// # Examples + /// + /// ``` + /// use slot::Slot; + /// + /// let s : Slot = Slot::new(); + /// s.put(123); + /// + /// assert!(! s.is_empty()); + /// s.clear(); + /// assert!(s.is_empty()); + /// + /// s.clear(); // this is allowed - no-op if empty + /// ``` + pub fn clear(&self) { + self.v.replace(None); + } + + /// Borrow. you must call `.as_ref().unwrap()` to get a reference to the inner value, + /// but this reference lives only as long as the `Ref` it came from, so it can't be + /// done inside the function. + /// + /// It is possible to get `Ref(None)` if the container is currently empty. + /// + /// # Examples + /// + /// ``` + /// use slot::Slot; + /// use std::rc::Rc; + /// + /// let s : Slot = Slot::new(); + /// s.borrow(); // no panic + /// + /// let s = Slot::new_with("aaa".to_string()); + /// + /// assert_eq!("aaa", format!("{}", s.borrow().as_ref().unwrap())); + /// ``` + pub fn borrow(&self) -> Ref>> { + self.v.borrow() + } +} + +impl fmt::Display for Slot +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.is_empty() { + true => write!(f, "nil"), + false => write!(f, "{}", self.borrow().as_ref().unwrap()), + } + } +} + +impl fmt::Debug for Slot +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let b = self.borrow(); + match b.is_none() { + true => write!(f, "Slot(None)"), + false => { + let unwrapped = b.as_ref().unwrap(); + if f.alternate() { + write!( + f, + "Slot(Rc({}+{}) -> {:#?})", + Rc::strong_count(unwrapped), + Rc::weak_count(unwrapped), + unwrapped + ) + } else { + write!( + f, + "Slot(Rc({}+{}) -> {:?})", + Rc::strong_count(unwrapped), + Rc::weak_count(unwrapped), + unwrapped + ) + } + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::Slot; + + #[test] + #[should_panic] + fn take_from_empty_panics() { + let s: Slot = Slot::new(); + let _s = s.take(); + } + + #[test] + #[should_panic] + fn take_from_borrowed_panics() { + let s: Slot = Slot::new(); + let _a = s.borrow(); + let _s = s.take(); + } + + #[test] + #[should_panic] + fn lease_from_empty_panics() { + let s: Slot = Slot::new(); + let _a = s.lease(); + } +}