diff --git a/CHANGELOG.md b/CHANGELOG.md index 50f34f8..215ea31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ Unreleased ========== +* Add `UnrestrictedSlots` with relaxed access control [@chrysn] [@bugadani] * Implement read-only `.iter()` [@bugadani] * Implement `Default` for `Slots` [@bugadani] diff --git a/src/iterator.rs b/src/iterator.rs new file mode 100644 index 0000000..0aea1fb --- /dev/null +++ b/src/iterator.rs @@ -0,0 +1,92 @@ +use crate::private::Entry; + +/// Read-only iterator to access all occupied slots. +pub struct Iter<'a, IT> { + inner: core::slice::Iter<'a, Entry>, +} + +impl<'a, IT> Iter<'a, IT> { + pub(crate) fn from_entry_slice(inner: &'a [Entry]) -> Self { + Self { + inner: inner.into_iter(), + } + } +} + +impl<'a, IT> Iterator for Iter<'a, IT> { + type Item = &'a IT; + + fn next(&mut self) -> Option { + while let Some(slot) = self.inner.next() { + if let Entry::Used(ref item) = slot { + return Some(item); + } + } + None + } +} + +/// Read-write iterator to access all occupied slots. +pub struct IterMut<'a, IT> { + inner: core::slice::IterMut<'a, Entry>, +} + +impl<'a, IT> IterMut<'a, IT> { + pub(crate) fn from_entry_slice(inner: &'a mut [Entry]) -> Self { + Self { + inner: inner.iter_mut(), + } + } +} + +impl<'a, IT> Iterator for IterMut<'a, IT> { + type Item = &'a mut IT; + + fn next(&mut self) -> Option { + while let Some(slot) = self.inner.next() { + if let Entry::Used(ref mut item) = slot { + return Some(item); + } + } + None + } +} + +#[cfg(test)] +mod iter_test { + use crate::{consts::U3, slots::Slots, unrestricted::UnrestrictedSlots}; + + #[test] + fn sanity_check() { + let mut slots: Slots<_, U3> = Slots::new(); + + let _k1 = slots.store(1).unwrap(); + let k2 = slots.store(2).unwrap(); + let _k3 = slots.store(3).unwrap(); + + slots.take(k2); + + let mut iter = slots.iter(); + // iterator does not return elements in order of store + assert_eq!(Some(&3), iter.next()); + assert_eq!(Some(&1), iter.next()); + assert_eq!(None, iter.next()); + + for &_ in slots.iter() {} + } + + #[test] + fn test_mut() { + let mut slots: UnrestrictedSlots<_, U3> = UnrestrictedSlots::new(); + + let _k1 = slots.store(1).unwrap(); + let k2 = slots.store(2).unwrap(); + let _k3 = slots.store(3).unwrap(); + + for k in slots.iter_mut() { + *k *= 2; + } + + assert_eq!(Some(4), slots.take(k2)); + } +} diff --git a/src/lib.rs b/src/lib.rs index e4c1ffa..baa54c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,403 +1,19 @@ -//! This crate provides a heapless slab allocator with strict access control. +//! This crate provides a heapless, fixed size, unordered data structure, inspired by SlotMap. //! -//! Slots implements a "heapless", fixed size, unordered data structure, -//! inspired by SlotMap. +//! The following basic operations (all of them `O(1)`) are defined for Slots: +//! - Store: store data and retrieve a handle for later access +//! - Read, modify: use the given handle to access the data without removal +//! - Take: use the given handle to remove data //! -//! # Store data -//! -//! When a piece of data is stored in the collection, a [`Key`] object is -//! returned. This key represents the owner of the stored data: -//! it is required to modify or remove (take out) the stored data. -//! -//! To ensure the stored data is always valid as long as the key exists, -//! the key can't be cloned. -//! -//! ```rust -//! use slots::Slots; -//! use slots::consts::U2; -//! -//! let mut slots: Slots<_, U2> = Slots::new(); // Capacity of 2 elements -//! -//! // Store elements -//! let k1 = slots.store(2).unwrap(); -//! let k2 = slots.store(4).unwrap(); -//! -//! // Now that the collection is full, the next store will fail and -//! // return an Err object that holds the original value we wanted to store. -//! let k3 = slots.store(8); -//! assert_eq!(k3.err(), Some(8)); -//! -//! // Storage statistics -//! assert_eq!(2, slots.capacity()); // this instance can hold at most 2 elements -//! assert_eq!(2, slots.count()); // there are currently 2 elements stored -//! ``` -//! -//! # Remove data -//! -//! Removing data from a Slots collection invalidates its key. Because of this, -//! the [`take`] method consumes the key. -//! -//! ```rust -//! # use slots::Slots; -//! # use slots::consts::U2; -//! # -//! # let mut slots: Slots<_, U2> = Slots::new(); -//! # -//! # let k = slots.store(2).unwrap(); -//! # let _ = slots.store(4).unwrap(); -//! # -//! // initially we have 2 elements in the collection -//! assert_eq!(2, slots.count()); -//! -//! // remove an element -//! slots.take(k); -//! -//! // removing also decreases the count of stored elements -//! assert_eq!(1, slots.count()); -//! ``` -//! -//! ```rust{compile_fail} -//! # use slots::Slots; -//! # use slots::consts::U1; -//! # -//! # let mut slots: Slots<_, U1> = Slots::new(); -//! # -//! let k1 = slots.store(2).unwrap(); -//! -//! slots.take(k1); // k1 is consumed and can no longer be used -//! slots.take(k1); // trying to use it again will cause a compile error -//! ``` -//! -//! # Access stored data -//! -//! The key can be used to read or modify the stored data. This is done by passing a `FnOnce` closure -//! to the [`read`] and [`modify`] methods. Whatever the closures return, will be returned by the methods. -//! -//! ```rust -//! # use slots::Slots; -//! # use slots::consts::U2; -//! # -//! # let mut slots: Slots<_, U2> = Slots::new(); -//! # -//! # let k1 = slots.store(2).unwrap(); -//! let k2 = slots.store(4).unwrap(); -//! // Read element without modification -//! // closure can be used to transform element -//! assert_eq!(3, slots.read(&k2, |&e| e - 1)); -//! -//! // Modify a stored element and return a derivative -//! assert_eq!(3, slots.modify(&k2, |e| { -//! *e = 2 + *e; -//! 3 -//! })); -//! // The value behind k2 has changed -//! assert_eq!(6, slots.read(&k2, |&e| e)); -//! ``` -//! -//! # Read using a numerical index -//! -//! It's possible to extract the index of the allocated slot from the [`Key`] object, using the [`index`] method. -//! Because this returns a number with the `usize` type, it is not guaranteed to refer to valid data. -//! -//! ```rust -//! # use slots::Slots; -//! # use slots::consts::U2; -//! # -//! # let mut slots: Slots<_, U2> = Slots::new(); -//! let k1 = slots.store(2).unwrap(); -//! let idx = k1.index(); -//! slots.take(k1); // idx no longer points to valid data -//! -//! assert_eq!(None, slots.try_read(idx, |&e| e*2)); // reading from a freed slot fails -//! ``` -//! -//! # Passing around Slots -//! -//! When you need to work with arbitrarily sized Slots objects, -//! you need to specify that the [`Size`] trait is implemented for -//! the parameter N. -//! ``` -//! use slots::{Slots, Size, Key}; -//! -//! fn examine(slots: &Slots, keys: &[Key]) -//! where N: Size, -//! { -//! unimplemented!(); -//! } -//! ``` -//! -//! [`Key`]: ./struct.Key.html -//! [`Size`]: ./trait.Size.html -//! [`index`]: ./struct.Key.html#method.index -//! [`take`]: ./struct.Slots.html#method.take -//! [`read`]: ./struct.Slots.html#method.read -//! [`modify`]: ./struct.Slots.html#method.modify +//! There are two variations of this data structure: +//! * [`Slots`](./slots/index.html), where elements can only be modified using a `Key` that can't be copied +//! * [`UnrestrictedSlots`](./unrestricted/index.html), where elements are free to be modified by anyone #![cfg_attr(not(test), no_std)] +pub mod iterator; mod private; - -use core::marker::PhantomData; -use core::mem::replace; -use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; - -use private::Entry; +pub mod slots; +pub mod unrestricted; pub use generic_array::typenum::consts; - -/// The key used to access stored elements. -/// -/// **Important:** It should only be used to access the same collection that returned it. -/// When the `verify_owner` feature is disabled, extra care must be taken to ensure this constraint. -#[derive(Debug)] -pub struct Key { - #[cfg(feature = "verify_owner")] - owner_id: usize, - index: usize, - _item_marker: PhantomData, - _size_marker: PhantomData, -} - -/// Alias of [`ArrayLength`](../generic_array/trait.ArrayLength.html) -pub trait Size: ArrayLength> {} -impl Size for T where T: ArrayLength> {} - -impl Key { - fn new(owner: &Slots, idx: usize) -> Self - where - N: Size, - { - Self { - #[cfg(feature = "verify_owner")] - owner_id: owner.id, - index: idx, - _item_marker: PhantomData, - _size_marker: PhantomData, - } - } - - pub fn index(&self) -> usize { - self.index - } -} - -/// Data type that stores values and returns a key that can be used to manipulate -/// the stored values. -/// Values can be read by anyone but can only be modified using the key. -pub struct Slots -where - N: Size, -{ - #[cfg(feature = "verify_owner")] - id: usize, - items: GenericArray, N>, - next_free: usize, - count: usize, -} - -/// Read-only iterator to access all occupied slots. -pub struct Iter<'a, IT> { - inner: core::slice::Iter<'a, private::Entry>, -} - -impl<'a, IT> Iterator for Iter<'a, IT> { - type Item = &'a IT; - - fn next(&mut self) -> Option { - while let Some(slot) = self.inner.next() { - if let Entry::Used(ref item) = slot { - return Some(item); - } - } - None - } -} - -#[cfg(test)] -mod iter_test { - use super::{consts::U3, Slots}; - - #[test] - fn sanity_check() { - let mut slots: Slots<_, U3> = Slots::new(); - - let _k1 = slots.store(1).unwrap(); - let k2 = slots.store(2).unwrap(); - let _k3 = slots.store(3).unwrap(); - - slots.take(k2); - - let mut iter = slots.iter(); - // iterator does not return elements in order of store - assert_eq!(Some(&3), iter.next()); - assert_eq!(Some(&1), iter.next()); - assert_eq!(None, iter.next()); - - for &_ in slots.iter() {} - } -} - -#[cfg(feature = "verify_owner")] -fn new_instance_id() -> usize { - use core::sync::atomic::{AtomicUsize, Ordering}; - - static COUNTER: AtomicUsize = AtomicUsize::new(0); - - COUNTER.fetch_add(1, Ordering::Relaxed) -} - -impl Default for Slots -where - N: Size, -{ - fn default() -> Self { - Self::new() - } -} - -impl Slots -where - N: Size, -{ - /// Creates a new, empty Slots object. - pub fn new() -> Self { - let size = N::to_usize(); - - Self { - #[cfg(feature = "verify_owner")] - id: new_instance_id(), - items: GenericArray::generate(|i| { - i.checked_sub(1) - .map(Entry::EmptyNext) - .unwrap_or(Entry::EmptyLast) - }), - next_free: size.saturating_sub(1), // edge case: N == 0 - count: 0, - } - } - - /// Returns a read-only iterator. - /// The iterator can be used to read data from all occupied slots. - /// - /// **Note:** Do not rely on the order in which the elements are returned. - pub fn iter(&self) -> Iter { - Iter { - inner: self.items.iter(), - } - } - - #[cfg(feature = "verify_owner")] - fn verify_key(&self, key: &Key) { - assert_eq!(key.owner_id, self.id, "Key used in wrong instance"); - } - - #[cfg(not(feature = "verify_owner"))] - fn verify_key(&self, _key: &Key) {} - - /// Returns the number of slots - pub fn capacity(&self) -> usize { - N::to_usize() - } - - /// Returns the number of occupied slots - pub fn count(&self) -> usize { - self.count - } - - fn full(&self) -> bool { - self.count == self.capacity() - } - - fn free(&mut self, idx: usize) { - debug_assert!(self.count != 0, "Free called on an empty collection"); - - self.items[idx] = if self.full() { - Entry::EmptyLast - } else { - Entry::EmptyNext(self.next_free) - }; - - self.next_free = idx; // the freed element will always be the top of the free stack - self.count -= 1; - } - - fn alloc(&mut self) -> Option { - if self.full() { - // no free slot - None - } else { - // next_free points to the top of the free stack - let index = self.next_free; - - self.next_free = match self.items[index] { - Entry::EmptyNext(n) => n, // pop the stack - Entry::EmptyLast => 0, // replace last element with anything - _ => unreachable!("Non-empty item in entry behind free chain"), - }; - self.count += 1; - Some(index) - } - } - - /// Store an element in a free slot and return the key to access it. - pub fn store(&mut self, item: IT) -> Result, IT> { - match self.alloc() { - Some(i) => { - self.items[i] = Entry::Used(item); - Ok(Key::new(self, i)) - } - None => Err(item), - } - } - - /// Remove and return the element that belongs to the key. - pub fn take(&mut self, key: Key) -> IT { - self.verify_key(&key); - - if let Entry::Used(item) = replace(&mut self.items[key.index], Entry::EmptyLast) { - self.free(key.index); - item - } else { - unreachable!("Invalid key"); - } - } - - /// Read the element that belongs to the key. - /// - /// This operation does not move ownership so the `function` callback must be used - /// to access the stored element. The callback may return arbitrary derivative of the element. - pub fn read(&self, key: &Key, function: impl FnOnce(&IT) -> T) -> T { - self.verify_key(&key); - - self.try_read(key.index, function).expect("Invalid key") - } - - /// Read the element that belongs to a particular index. Since the index may point to - /// a free slot or outside the collection, this operation may return None without invoking the callback. - /// - /// This operation does not move ownership so the `function` callback must be used - /// to access the stored element. The callback may return arbitrary derivative of the element. - pub fn try_read(&self, key: usize, function: impl FnOnce(&IT) -> T) -> Option { - if key >= self.capacity() { - None - } else { - match &self.items[key] { - Entry::Used(item) => Some(function(&item)), - _ => None, - } - } - } - - /// Access the element that belongs to the key for modification. - /// - /// This operation does not move ownership so the `function` callback must be used - /// to access the stored element. The callback may return arbitrary derivative of the element. - pub fn modify(&mut self, key: &Key, function: impl FnOnce(&mut IT) -> T) -> T { - self.verify_key(&key); - - match self.items[key.index] { - Entry::Used(ref mut item) => function(item), - _ => unreachable!("Invalid key"), - } - } -} diff --git a/src/private.rs b/src/private.rs index 8683e63..af0053b 100644 --- a/src/private.rs +++ b/src/private.rs @@ -7,3 +7,9 @@ pub enum Entry { EmptyNext(usize), EmptyLast, } + +impl Default for Entry { + fn default() -> Self { + Entry::EmptyLast + } +} diff --git a/src/slots.rs b/src/slots.rs new file mode 100644 index 0000000..19851bc --- /dev/null +++ b/src/slots.rs @@ -0,0 +1,388 @@ +//! Slots object that provides strict access control for the stored data. +//! +//! Data type that stores values and returns a key that can be used to manipulate +//! the stored values. +//! Values can be read by anyone but can only be modified using the key. +//! +//! # Store data +//! +//! When a piece of data is stored in the collection, a [`Key`] object is +//! returned. This key represents the owner of the stored data: +//! it is required to modify or remove (take out) the stored data. +//! +//! To ensure the stored data is always valid as long as the key exists, +//! the key can't be cloned. +//! +//! ```rust +//! use slots::slots::Slots; +//! use slots::consts::U2; +//! +//! let mut slots: Slots<_, U2> = Slots::new(); // Capacity of 2 elements +//! +//! // Store elements +//! let k1 = slots.store(2).unwrap(); +//! let k2 = slots.store(4).unwrap(); +//! +//! assert_eq!(true, slots.is_full()); +//! +//! // Now that the collection is full, the next store will fail and +//! // return an Err object that holds the original value we wanted to store. +//! let k3 = slots.store(8); +//! assert_eq!(k3.err(), Some(8)); +//! +//! // Storage statistics +//! assert_eq!(2, slots.capacity()); // this instance can hold at most 2 elements +//! assert_eq!(2, slots.count()); // there are currently 2 elements stored +//! ``` +//! +//! # Remove data +//! +//! Removing data from a Slots collection invalidates its key. Because of this, +//! the [`take`] method consumes the key. +//! +//! ```rust +//! # use slots::slots::Slots; +//! # use slots::consts::U2; +//! # +//! # let mut slots: Slots<_, U2> = Slots::new(); +//! # +//! # let k = slots.store(2).unwrap(); +//! # let _ = slots.store(4).unwrap(); +//! # +//! // initially we have 2 elements in the collection +//! assert_eq!(2, slots.count()); +//! +//! // remove an element +//! slots.take(k); +//! +//! // removing also decreases the count of stored elements +//! assert_eq!(1, slots.count()); +//! ``` +//! +//! ```rust{compile_fail} +//! # use slots::slots::Slots; +//! # use slots::consts::U1; +//! # +//! # let mut slots: Slots<_, U1> = Slots::new(); +//! # +//! let k1 = slots.store(2).unwrap(); +//! +//! slots.take(k1); // k1 is consumed and can no longer be used +//! slots.take(k1); // trying to use it again will cause a compile error +//! ``` +//! +//! # Access stored data +//! +//! The key can be used to read or modify the stored data. This is done by passing a `FnOnce` closure +//! to the [`read`] and [`modify`] methods. Whatever the closures return, will be returned by the methods. +//! +//! ```rust +//! # use slots::slots::Slots; +//! # use slots::consts::U2; +//! # +//! # let mut slots: Slots<_, U2> = Slots::new(); +//! # +//! # let k1 = slots.store(2).unwrap(); +//! let k2 = slots.store(4).unwrap(); +//! // Read element without modification +//! // closure can be used to transform element +//! assert_eq!(3, slots.read(&k2, |&e| e - 1)); +//! +//! // Modify a stored element and return a derivative +//! assert_eq!(3, slots.modify(&k2, |e| { +//! *e = 2 + *e; +//! 3 +//! })); +//! // The value behind k2 has changed +//! assert_eq!(6, slots.read(&k2, |&e| e)); +//! ``` +//! +//! # Read using a numerical index +//! +//! It's possible to extract the index of the allocated slot from the [`Key`] object, using the [`index`] method. +//! Because this returns a number with the `usize` type, it is not guaranteed to refer to valid data. +//! +//! ```rust +//! # use slots::slots::Slots; +//! # use slots::consts::U2; +//! # +//! # let mut slots: Slots<_, U2> = Slots::new(); +//! let k1 = slots.store(2).unwrap(); +//! let idx = k1.index(); +//! slots.take(k1); // idx no longer points to valid data +//! +//! assert_eq!(None, slots.try_read(idx, |&e| e*2)); // reading from a freed slot fails +//! ``` +//! +//! # Passing around Slots +//! +//! When you need to work with arbitrarily sized Slots objects, +//! you need to specify that the [`Size`] trait is implemented for +//! the parameter N. +//! ``` +//! use slots::slots::{Slots, Size, Key}; +//! +//! fn examine(slots: &Slots, keys: &[Key]) +//! where N: Size, +//! { +//! unimplemented!(); +//! } +//! ``` +//! +//! [`Key`]: ./struct.Key.html +//! [`Size`]: ../unrestricted/trait.Size.html +//! [`index`]: ./struct.Key.html#method.index +//! [`take`]: ./struct.Slots.html#method.take +//! [`read`]: ./struct.Slots.html#method.read +//! [`modify`]: ./struct.Slots.html#method.modify +use core::marker::PhantomData; + +use crate::iterator::Iter; +use crate::unrestricted::UnrestrictedSlots; + +pub use crate::unrestricted::Size; + +/// The key used to access stored elements. +/// +/// **Important:** It should only be used to access the same collection that returned it. +/// When the `verify_owner` feature is disabled, extra care must be taken to ensure this constraint. +#[derive(Debug)] +pub struct Key { + #[cfg(feature = "verify_owner")] + owner_id: usize, + index: usize, + _item_marker: PhantomData, + _size_marker: PhantomData, +} + +impl Key { + fn new(owner: &Slots, idx: usize) -> Self + where + N: Size, + { + Self { + #[cfg(feature = "verify_owner")] + owner_id: owner.id, + index: idx, + _item_marker: PhantomData, + _size_marker: PhantomData, + } + } + + pub fn index(&self) -> usize { + self.index + } +} + +/// Slots object that provides strict access control for the stored data. +/// +/// The struct has two type parameters: +/// - `IT` is the type of the stored data +/// - `N` is the number of slots, which is a type-level constant provided by the `typenum` crate. +/// +/// For more information, see the [module level documentation](./index.html) +#[derive(Default)] +pub struct Slots +where + N: Size, +{ + #[cfg(feature = "verify_owner")] + id: usize, + inner: UnrestrictedSlots, +} + +#[cfg(feature = "verify_owner")] +fn new_instance_id() -> usize { + use core::sync::atomic::{AtomicUsize, Ordering}; + + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + COUNTER.fetch_add(1, Ordering::Relaxed) +} + +impl Slots +where + N: Size, +{ + /// Creates a new, empty Slots object. + pub fn new() -> Self { + Self { + #[cfg(feature = "verify_owner")] + id: new_instance_id(), + inner: UnrestrictedSlots::new(), + } + } + + /// Returns a read-only iterator. + /// The iterator can be used to read data from all occupied slots. + /// + /// **Note:** Do not rely on the order in which the elements are returned. + /// + /// ``` + /// # use slots::slots::Slots; + /// # use slots::consts::U4; + /// # let mut slots: Slots<_, U4> = Slots::new(); + /// slots.store(2).unwrap(); + /// slots.store(4).unwrap(); + /// slots.store(6).unwrap(); + /// + /// assert_eq!(true, slots.iter().any(|&x| x < 3)); + /// ``` + pub fn iter(&self) -> Iter { + self.inner.iter() + } + + #[cfg(feature = "verify_owner")] + fn verify_key(&self, key: &Key) { + assert_eq!(key.owner_id, self.id, "Key used in wrong instance"); + } + + #[cfg(not(feature = "verify_owner"))] + fn verify_key(&self, _key: &Key) {} + + /// Returns the number of slots + /// + /// ``` + /// # use slots::slots::Slots; + /// # use slots::consts::U4; + /// let slots: Slots = Slots::new(); + /// + /// assert_eq!(4, slots.capacity()); + /// ``` + pub fn capacity(&self) -> usize { + N::to_usize() + } + + /// Returns the number of occupied slots + /// + /// ``` + /// # use slots::slots::Slots; + /// # use slots::consts::U4; + /// let mut slots: Slots<_, U4> = Slots::new(); + /// + /// assert_eq!(0, slots.count()); + /// + /// slots.store(3).unwrap(); + /// slots.store(6).unwrap(); + /// + /// assert_eq!(2, slots.count()); + /// ``` + pub fn count(&self) -> usize { + self.inner.count() + } + + /// Returns whether all the slots are occupied and the next [`store()`](#method.store) will fail. + /// + /// ``` + /// # use slots::slots::Slots; + /// # use slots::consts::U4; + /// let mut slots: Slots<_, U4> = Slots::new(); + /// + /// slots.store(3).unwrap(); + /// slots.store(4).unwrap(); + /// slots.store(5).unwrap(); + /// + /// assert_eq!(false, slots.is_full()); + /// + /// slots.store(6).unwrap(); + /// + /// assert_eq!(true, slots.is_full()); + /// ``` + pub fn is_full(&self) -> bool { + self.inner.is_full() + } + + /// Store an element in a free slot and return the key to access it. + /// + /// Storing a variable takes ownership over it. If the storage is full, + /// the inserted data is returned in the return value. + pub fn store(&mut self, item: IT) -> Result, IT> { + self.inner.store(item).map(|idx| Key::new(self, idx)) + } + + /// Remove and return the element that belongs to the key. + pub fn take(&mut self, key: Key) -> IT { + self.verify_key(&key); + + self.inner.take(key.index).expect("Invalid key") + } + + /// Read the element that belongs to the key. + /// + /// This operation does not move ownership so the `function` callback must be used + /// to access the stored element. The callback may return arbitrary derivative of the element. + /// + /// ``` + /// # use slots::slots::Slots; + /// # use slots::consts::U4; + /// # let mut slots: Slots<_, U4> = Slots::new(); + /// + /// let k = slots.store(3).unwrap(); + /// + /// assert_eq!(4, slots.read(&k, |elem| { + /// elem + 1 + /// })); + /// ``` + pub fn read(&self, key: &Key, function: impl FnOnce(&IT) -> T) -> T { + self.verify_key(&key); + + self.inner.read(key.index, function).expect("Invalid key") + } + + /// Read the element that belongs to a particular index. Since the index may point to + /// a free slot or outside the collection, this operation may return None without invoking the callback. + /// + /// This operation does not move ownership so the `function` callback must be used + /// to access the stored element. The callback may return arbitrary derivative of the element. + /// + /// This operation is fallible. If `index` addresses a free slot, `None` is returned. + /// + /// ``` + /// # use slots::slots::Slots; + /// # use slots::consts::U4; + /// # let mut slots: Slots<_, U4> = Slots::new(); + /// + /// let k = slots.store(3).unwrap(); + /// let idx = k.index(); + /// + /// assert_eq!(Some(4), slots.try_read(idx, |elem| { + /// elem + 1 + /// })); + /// + /// slots.take(k); + /// + /// assert_eq!(None, slots.try_read(idx, |elem| { + /// elem + 1 + /// })); + /// ``` + pub fn try_read(&self, index: usize, function: impl FnOnce(&IT) -> T) -> Option { + self.inner.read(index, function) + } + + /// Access the element that belongs to the key for modification. + /// + /// This operation does not move ownership so the `function` callback must be used + /// to access the stored element. The callback may return arbitrary derivative of the element. + /// + /// ``` + /// # use slots::slots::Slots; + /// # use slots::consts::U4; + /// # let mut slots: Slots<_, U4> = Slots::new(); + /// + /// let k = slots.store(3).unwrap(); + /// + /// assert_eq!("found", slots.modify(&k, |elem| { + /// *elem = *elem + 1; + /// + /// "found" + /// })); + /// + /// // Assert that the stored data was modified + /// assert_eq!(4, slots.take(k)); + /// ``` + pub fn modify(&mut self, key: &Key, function: impl FnOnce(&mut IT) -> T) -> T { + self.verify_key(&key); + + self.inner.modify(key.index, function).expect("Invalid key") + } +} diff --git a/src/unrestricted.rs b/src/unrestricted.rs new file mode 100644 index 0000000..81986c2 --- /dev/null +++ b/src/unrestricted.rs @@ -0,0 +1,309 @@ +//! Slots object that provides an unrestricted access control for the stored data. +//! +//! Data type that stores values and returns an index that can be used to manipulate +//! the stored values. +//! +//! Unlike [`Slots`], it's not guaranteed that the accessed slot has valid data. +//! For this reason, the data access methods are always fallible, meaning they return +//! None when a free slot is addressed. +//! +//! This structure is also susceptible to the [ABA problem](https://en.wikipedia.org/wiki/ABA_problem). +//! +//! # Store data +//! +//! When a piece of data is stored in the collection, a handle is returned. This handle +//! identifies the slot and can be used to access the data. Unlike with [`Slots`], this +//! handle is a `usize` which can be freely copied and shared. +//! +//! There should be no assumptions made on the value of the handle, except that it is `0 <= handle < N` +//! where N is the capacity. +//! +//! ```rust +//! use slots::unrestricted::UnrestrictedSlots; +//! use slots::consts::U2; +//! +//! let mut slots: UnrestrictedSlots<_, U2> = UnrestrictedSlots::new(); // Capacity of 2 elements +//! +//! // Store elements +//! let k1 = slots.store(2).unwrap(); +//! let k2 = slots.store(4).unwrap(); +//! +//! // Now that the collection is full, the next store will fail and +//! // return an Err object that holds the original value we wanted to store. +//! let k3 = slots.store(8); +//! assert_eq!(k3.err(), Some(8)); +//! +//! // Storage statistics +//! assert_eq!(2, slots.capacity()); // this instance can hold at most 2 elements +//! assert_eq!(2, slots.count()); // there are currently 2 elements stored +//! ``` +//! +//! [`Slots`]: ../slots/index.html + +use core::mem::replace; +use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; + +use crate::iterator::*; +use crate::private::Entry; + +/// Alias of [`ArrayLength`](../generic_array/trait.ArrayLength.html) +pub trait Size: ArrayLength> {} +impl Size for T where T: ArrayLength> {} + +/// Slots object that provides an unrestricted access control for the stored data. +/// +/// The struct has two type parameters: +/// - `IT` is the type of the stored data +/// - `N` is the number of slots, which is a type-level constant provided by the `typenum` crate. +/// +/// For more information, see the [module level documentation](./index.html) +#[derive(Default)] +pub struct UnrestrictedSlots +where + N: Size, +{ + items: GenericArray, N>, + next_free: usize, + count: usize, +} + +impl UnrestrictedSlots +where + N: Size, +{ + /// Creates a new, empty UnrestrictedSlots object. + pub fn new() -> Self { + Self { + items: GenericArray::generate(|i| { + i.checked_sub(1) + .map(Entry::EmptyNext) + .unwrap_or(Entry::EmptyLast) + }), + next_free: N::USIZE.saturating_sub(1), // edge case: N == 0 + count: 0, + } + } + + /// Returns a read-only iterator. + /// The iterator can be used to read data from all occupied slots. + /// + /// **Note:** Do not rely on the order in which the elements are returned. + /// + /// ``` + /// # use slots::unrestricted::UnrestrictedSlots; + /// # use slots::consts::U4; + /// # let mut slots: UnrestrictedSlots<_, U4> = UnrestrictedSlots::new(); + /// slots.store(2).unwrap(); + /// slots.store(4).unwrap(); + /// slots.store(6).unwrap(); + /// + /// assert_eq!(true, slots.iter().any(|&x| x < 3)); + /// ``` + pub fn iter(&self) -> Iter { + Iter::from_entry_slice(self.items.as_slice()) + } + + /// Returns a read-write iterator. + /// The iterator can be used to read and modify data from all occupied slots, but it can't remove data. + /// + /// **Note:** Do not rely on the order in which the elements are returned. + /// + /// ``` + /// # use slots::unrestricted::UnrestrictedSlots; + /// # use slots::consts::U4; + /// # let mut slots: UnrestrictedSlots<_, U4> = UnrestrictedSlots::new(); + /// let k = slots.store(2).unwrap(); + /// slots.store(4).unwrap(); + /// slots.store(6).unwrap(); + /// + /// for mut x in slots.iter_mut() { + /// *x *= 2; + /// } + /// + /// assert_eq!(4, slots.take(k).unwrap()); + /// ``` + pub fn iter_mut(&mut self) -> IterMut { + IterMut::from_entry_slice(self.items.as_mut_slice()) + } + + /// Returns the number of slots + /// + /// ``` + /// # use slots::unrestricted::UnrestrictedSlots; + /// # use slots::consts::U4; + /// let slots: UnrestrictedSlots = UnrestrictedSlots::new(); + /// + /// assert_eq!(4, slots.capacity()); + /// ``` + pub fn capacity(&self) -> usize { + N::USIZE + } + + /// Returns the number of occupied slots + /// + /// ``` + /// # use slots::unrestricted::UnrestrictedSlots; + /// # use slots::consts::U4; + /// let mut slots: UnrestrictedSlots<_, U4> = UnrestrictedSlots::new(); + /// + /// assert_eq!(0, slots.count()); + /// + /// slots.store(3).unwrap(); + /// slots.store(6).unwrap(); + /// + /// assert_eq!(2, slots.count()); + /// ``` + pub fn count(&self) -> usize { + self.count + } + + /// Returns whether all the slots are occupied and the next [`store()`](#method.store) will fail. + /// + /// ``` + /// # use slots::unrestricted::UnrestrictedSlots; + /// # use slots::consts::U4; + /// let mut slots: UnrestrictedSlots<_, U4> = UnrestrictedSlots::new(); + /// + /// slots.store(3).unwrap(); + /// slots.store(4).unwrap(); + /// slots.store(5).unwrap(); + /// + /// assert_eq!(false, slots.is_full()); + /// + /// slots.store(6).unwrap(); + /// + /// assert_eq!(true, slots.is_full()); + /// ``` + pub fn is_full(&self) -> bool { + self.count == self.capacity() + } + + fn free(&mut self, idx: usize) { + debug_assert!(self.count != 0, "Free called on an empty collection"); + + self.items[idx] = if self.is_full() { + Entry::EmptyLast + } else { + Entry::EmptyNext(self.next_free) + }; + + self.next_free = idx; // the freed element will always be the top of the free stack + self.count -= 1; + } + + fn alloc(&mut self) -> Option { + if self.is_full() { + // no free slot + None + } else { + // next_free points to the top of the free stack + let index = self.next_free; + + self.next_free = match self.items[index] { + Entry::EmptyNext(n) => n, // pop the stack + Entry::EmptyLast => 0, // replace last element with anything + _ => unreachable!("Non-empty item in entry behind free chain"), + }; + self.count += 1; + Some(index) + } + } + + /// Store an element in a free slot and return the key to access it. + /// + /// Storing a variable takes ownership over it. If the storage is full, + /// the inserted data is returned in the return value. + pub fn store(&mut self, item: IT) -> Result { + match self.alloc() { + Some(i) => { + self.items[i] = Entry::Used(item); + Ok(i) + } + None => Err(item), + } + } + + /// Remove and return the element that belongs to the key. + /// + /// This operation is fallible. If `key` addresses a free slot, `None` is returned. + pub fn take(&mut self, key: usize) -> Option { + if let Entry::Used(item) = replace(&mut self.items[key], Entry::EmptyLast) { + self.free(key); + Some(item) + } else { + None + } + } + + /// Read the element that belongs to a particular index. Since the index may point to + /// a free slot or outside the collection, this operation may return None without invoking the callback. + /// + /// This operation does not move ownership so the `function` callback must be used + /// to access the stored element. The callback may return arbitrary derivative of the element. + /// + /// This operation is fallible. If `key` addresses a free slot, `None` is returned. + /// + /// ``` + /// # use slots::unrestricted::UnrestrictedSlots; + /// # use slots::consts::U4; + /// # let mut slots: UnrestrictedSlots<_, U4> = UnrestrictedSlots::new(); + /// + /// let k = slots.store(3).unwrap(); + /// + /// assert_eq!(Some(4), slots.read(k, |elem| { + /// elem + 1 + /// })); + /// + /// slots.take(k); + /// + /// assert_eq!(None, slots.read(k, |elem| { + /// elem + 1 + /// })); + /// ``` + pub fn read(&self, key: usize, function: impl FnOnce(&IT) -> T) -> Option { + if key >= self.capacity() { + None + } else { + match &self.items[key] { + Entry::Used(item) => Some(function(&item)), + _ => None, + } + } + } + + /// Access the element that belongs to the key for modification. + /// + /// This operation does not move ownership so the `function` callback must be used + /// to access the stored element. The callback may return arbitrary derivative of the element. + /// + /// This operation is fallible. If `key` addresses a free slot, `None` is returned. + /// + /// ``` + /// # use slots::unrestricted::UnrestrictedSlots; + /// # use slots::consts::U4; + /// # let mut slots: UnrestrictedSlots<_, U4> = UnrestrictedSlots::new(); + /// + /// let k = slots.store(3).unwrap(); + /// + /// assert_eq!(Some("found"), slots.modify(k, |elem| { + /// *elem = *elem + 1; + /// + /// "found" + /// })); + /// + /// // Assert that the stored data was modified + /// assert_eq!(Some(4), slots.take(k)); + /// + /// assert_eq!(None, slots.modify(k, |elem| { + /// *elem = *elem + 1; + /// + /// "found" + /// })); + /// ``` + pub fn modify(&mut self, key: usize, function: impl FnOnce(&mut IT) -> T) -> Option { + match self.items[key] { + Entry::Used(ref mut item) => Some(function(item)), + _ => None, + } + } +} diff --git a/tests/test.rs b/tests/test.rs index b028f10..319c40c 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,5 +1,5 @@ use slots::consts::*; -use slots::Slots; +use slots::slots::Slots; #[test] fn key_can_be_used_to_read_value() {