From 5eed6e5254b84951dc774e087a1dc7f189dd4cfd Mon Sep 17 00:00:00 2001 From: chrysn Date: Mon, 18 May 2020 21:28:23 +0200 Subject: [PATCH 01/16] Basic implementation UnrestrictedSlots operate purely on unprotecetd indices, Slots gives all the protection around it. Originally done by @chrysn in #11 --- src/lib.rs | 147 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 40 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e4c1ffa..d595b2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,18 +181,26 @@ impl Key { } } +pub struct UnrestrictedSlots +where + N: Size, +{ + items: GenericArray, N>, + next_free: usize, + count: usize, +} + /// 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. +#[derive(Default)] pub struct Slots where N: Size, { #[cfg(feature = "verify_owner")] id: usize, - items: GenericArray, N>, - next_free: usize, - count: usize, + inner: UnrestrictedSlots, } /// Read-only iterator to access all occupied slots. @@ -246,7 +254,7 @@ fn new_instance_id() -> usize { COUNTER.fetch_add(1, Ordering::Relaxed) } -impl Default for Slots +impl Default for UnrestrictedSlots where N: Size, { @@ -255,23 +263,19 @@ where } } -impl Slots +impl UnrestrictedSlots 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 + next_free: N::USIZE.saturating_sub(1), // edge case: N == 0 count: 0, } } @@ -286,17 +290,9 @@ where } } - #[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() + N::USIZE } /// Returns the number of occupied slots @@ -340,28 +336,109 @@ where } /// Store an element in a free slot and return the key to access it. - pub fn store(&mut self, item: IT) -> Result, IT> { + pub fn store(&mut self, item: IT) -> Result { match self.alloc() { Some(i) => { self.items[i] = Entry::Used(item); - Ok(Key::new(self, i)) + Ok(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); + 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 + } + } - if let Entry::Used(item) = replace(&mut self.items[key.index], Entry::EmptyLast) { - self.free(key.index); - item + /// 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 read(&self, key: usize, function: impl FnOnce(&IT) -> T) -> Option { + if key >= self.capacity() { + None } else { - unreachable!("Invalid key"); + 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: usize, function: impl FnOnce(&mut IT) -> T) -> Option { + match self.items[key] { + Entry::Used(ref mut item) => Some(function(item)), + _ => None, + } + } +} + +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. + 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 + pub fn capacity(&self) -> usize { + N::to_usize() + } + + /// Returns the number of occupied slots + pub fn count(&self) -> usize { + self.inner.count + } + + fn full(&self) -> bool { + self.inner.full() + } + + /// Store an element in a free slot and return the key to access it. + 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 @@ -369,7 +446,7 @@ where 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") + self.inner.read(key.index, function).expect("Invalid key") } /// Read the element that belongs to a particular index. Since the index may point to @@ -378,14 +455,7 @@ where /// 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, - } - } + self.inner.read(key, function) } /// Access the element that belongs to the key for modification. @@ -395,9 +465,6 @@ where 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"), - } + self.inner.modify(key.index, function).expect("Invalid key") } } From fa602632b45673959f14f51a0c108a3e1718eb66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 20 May 2020 10:09:09 +0200 Subject: [PATCH 02/16] iter_mut for unrestricted implementation --- src/lib.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d595b2f..cd12919 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,6 +208,11 @@ pub struct Iter<'a, IT> { inner: core::slice::Iter<'a, private::Entry>, } +/// Read-write iterator to access all occupied slots. +pub struct IterMut<'a, IT> { + inner: core::slice::IterMut<'a, private::Entry>, +} + impl<'a, IT> Iterator for Iter<'a, IT> { type Item = &'a IT; @@ -221,9 +226,22 @@ impl<'a, IT> Iterator for Iter<'a, IT> { } } +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 super::{consts::U3, Slots}; + use super::{consts::U3, Slots, UnrestrictedSlots}; #[test] fn sanity_check() { @@ -243,6 +261,21 @@ mod iter_test { 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)); + } } #[cfg(feature = "verify_owner")] @@ -290,6 +323,12 @@ where } } + pub fn iter_mut(&mut self) -> IterMut { + IterMut { + inner: self.items.iter_mut(), + } + } + /// Returns the number of slots pub fn capacity(&self) -> usize { N::USIZE From f5f25792fb54c8b6c4fad0a1ea73bcd6b45e4c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 20 May 2020 16:58:56 +0200 Subject: [PATCH 03/16] Split up into multiple files --- src/iterator.rs | 92 +++++++++++++++++ src/lib.rs | 239 ++------------------------------------------ src/unrestricted.rs | 150 +++++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 233 deletions(-) create mode 100644 src/iterator.rs create mode 100644 src/unrestricted.rs diff --git a/src/iterator.rs b/src/iterator.rs new file mode 100644 index 0000000..280d809 --- /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 fn from_iter(inner: &'a [Entry]) -> Self { + Self { + inner: inner.into_iter() + } + } +} + +/// 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 fn from_iter(inner: &'a mut [Entry]) -> Self { + Self { + inner: inner.iter_mut() + } + } +} + +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 + } +} + +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, 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)); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index cd12919..9e10b88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,12 +136,13 @@ #![cfg_attr(not(test), no_std)] mod private; +mod unrestricted; +mod iterator; -use core::marker::PhantomData; -use core::mem::replace; -use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; +pub use unrestricted::*; +pub use iterator::*; -use private::Entry; +use core::marker::PhantomData; pub use generic_array::typenum::consts; @@ -158,10 +159,6 @@ pub struct Key { _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 @@ -181,15 +178,6 @@ impl Key { } } -pub struct UnrestrictedSlots -where - N: Size, -{ - items: GenericArray, N>, - next_free: usize, - count: usize, -} - /// 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. @@ -203,81 +191,6 @@ where inner: UnrestrictedSlots, } -/// Read-only iterator to access all occupied slots. -pub struct Iter<'a, IT> { - inner: core::slice::Iter<'a, private::Entry>, -} - -/// Read-write iterator to access all occupied slots. -pub struct IterMut<'a, IT> { - inner: core::slice::IterMut<'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 - } -} - -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 super::{consts::U3, Slots, 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)); - } -} - #[cfg(feature = "verify_owner")] fn new_instance_id() -> usize { use core::sync::atomic::{AtomicUsize, Ordering}; @@ -287,142 +200,6 @@ fn new_instance_id() -> usize { COUNTER.fetch_add(1, Ordering::Relaxed) } -impl Default for UnrestrictedSlots -where - N: Size, -{ - fn default() -> Self { - Self::new() - } -} - -impl UnrestrictedSlots -where - N: Size, -{ - /// Creates a new, empty Slots 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. - pub fn iter(&self) -> Iter { - Iter { - inner: self.items.iter(), - } - } - - pub fn iter_mut(&mut self) -> IterMut { - IterMut { - inner: self.items.iter_mut(), - } - } - - /// Returns the number of slots - pub fn capacity(&self) -> usize { - N::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 { - 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. - 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. - 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. - 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, - } - } -} - impl Slots where N: Size, @@ -459,11 +236,7 @@ where /// Returns the number of occupied slots pub fn count(&self) -> usize { - self.inner.count - } - - fn full(&self) -> bool { - self.inner.full() + self.inner.count() } /// Store an element in a free slot and return the key to access it. diff --git a/src/unrestricted.rs b/src/unrestricted.rs new file mode 100644 index 0000000..42db3b6 --- /dev/null +++ b/src/unrestricted.rs @@ -0,0 +1,150 @@ +use core::mem::replace; +use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; + +use crate::private::Entry; +use crate::iterator::*; + +/// Alias of [`ArrayLength`](../generic_array/trait.ArrayLength.html) +pub trait Size: ArrayLength> {} +impl Size for T where T: ArrayLength> {} + +pub struct UnrestrictedSlots +where + N: Size, +{ + items: GenericArray, N>, + next_free: usize, + count: usize, +} + +impl Default for UnrestrictedSlots +where + N: Size, +{ + fn default() -> Self { + Self::new() + } +} + +impl UnrestrictedSlots +where + N: Size, +{ + /// Creates a new, empty Slots 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. + pub fn iter(&self) -> Iter { + Iter::from_iter(self.items.as_slice()) + } + + pub fn iter_mut(&mut self) -> IterMut { + IterMut::from_iter(self.items.as_mut_slice()) + } + + /// Returns the number of slots + pub fn capacity(&self) -> usize { + N::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 { + 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. + 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. + 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. + 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, + } + } +} \ No newline at end of file From a57ed8e7e91abb58605e818799ca19ecc61d8b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 10:39:19 +0200 Subject: [PATCH 04/16] Move Slots implementation to new file, don't reexport everything --- src/iterator.rs | 8 +-- src/lib.rs | 157 +++----------------------------------------- src/slots.rs | 141 +++++++++++++++++++++++++++++++++++++++ src/unrestricted.rs | 4 +- tests/test.rs | 2 +- 5 files changed, 157 insertions(+), 155 deletions(-) create mode 100644 src/slots.rs diff --git a/src/iterator.rs b/src/iterator.rs index 280d809..19f4fd7 100644 --- a/src/iterator.rs +++ b/src/iterator.rs @@ -8,7 +8,7 @@ pub struct Iter<'a, IT> { impl<'a, IT> Iter<'a, IT> { pub fn from_iter(inner: &'a [Entry]) -> Self { Self { - inner: inner.into_iter() + inner: inner.into_iter(), } } } @@ -21,7 +21,7 @@ pub struct IterMut<'a, IT> { impl<'a, IT> IterMut<'a, IT> { pub fn from_iter(inner: &'a mut [Entry]) -> Self { Self { - inner: inner.iter_mut() + inner: inner.iter_mut(), } } } @@ -54,7 +54,7 @@ impl<'a, IT> Iterator for IterMut<'a, IT> { #[cfg(test)] mod iter_test { - use crate::{consts::U3, Slots, UnrestrictedSlots}; + use crate::{consts::U3, slots::Slots, unrestricted::UnrestrictedSlots}; #[test] fn sanity_check() { @@ -89,4 +89,4 @@ mod iter_test { assert_eq!(Some(4), slots.take(k2)); } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 9e10b88..a5f346d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ //! the key can't be cloned. //! //! ```rust -//! use slots::Slots; +//! use slots::slots::Slots; //! use slots::consts::U2; //! //! let mut slots: Slots<_, U2> = Slots::new(); // Capacity of 2 elements @@ -38,7 +38,7 @@ //! the [`take`] method consumes the key. //! //! ```rust -//! # use slots::Slots; +//! # use slots::slots::Slots; //! # use slots::consts::U2; //! # //! # let mut slots: Slots<_, U2> = Slots::new(); @@ -57,7 +57,7 @@ //! ``` //! //! ```rust{compile_fail} -//! # use slots::Slots; +//! # use slots::slots::Slots; //! # use slots::consts::U1; //! # //! # let mut slots: Slots<_, U1> = Slots::new(); @@ -74,7 +74,7 @@ //! to the [`read`] and [`modify`] methods. Whatever the closures return, will be returned by the methods. //! //! ```rust -//! # use slots::Slots; +//! # use slots::slots::Slots; //! # use slots::consts::U2; //! # //! # let mut slots: Slots<_, U2> = Slots::new(); @@ -100,7 +100,7 @@ //! Because this returns a number with the `usize` type, it is not guaranteed to refer to valid data. //! //! ```rust -//! # use slots::Slots; +//! # use slots::slots::Slots; //! # use slots::consts::U2; //! # //! # let mut slots: Slots<_, U2> = Slots::new(); @@ -117,7 +117,7 @@ //! you need to specify that the [`Size`] trait is implemented for //! the parameter N. //! ``` -//! use slots::{Slots, Size, Key}; +//! use slots::slots::{Slots, Size, Key}; //! //! fn examine(slots: &Slots, keys: &[Key]) //! where N: Size, @@ -135,148 +135,9 @@ #![cfg_attr(not(test), no_std)] +pub mod iterator; mod private; -mod unrestricted; -mod iterator; - -pub use unrestricted::*; -pub use iterator::*; - -use core::marker::PhantomData; +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, -} - -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. -#[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. - 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 - pub fn capacity(&self) -> usize { - N::to_usize() - } - - /// Returns the number of occupied slots - pub fn count(&self) -> usize { - self.inner.count() - } - - /// Store an element in a free slot and return the key to access it. - 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. - 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. - pub fn try_read(&self, key: usize, function: impl FnOnce(&IT) -> T) -> Option { - self.inner.read(key, 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. - 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/slots.rs b/src/slots.rs new file mode 100644 index 0000000..55c5c02 --- /dev/null +++ b/src/slots.rs @@ -0,0 +1,141 @@ +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 + } +} + +/// 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. +#[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. + 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 + pub fn capacity(&self) -> usize { + N::to_usize() + } + + /// Returns the number of occupied slots + pub fn count(&self) -> usize { + self.inner.count() + } + + /// Store an element in a free slot and return the key to access it. + 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. + 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. + pub fn try_read(&self, key: usize, function: impl FnOnce(&IT) -> T) -> Option { + self.inner.read(key, 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. + 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 index 42db3b6..208b099 100644 --- a/src/unrestricted.rs +++ b/src/unrestricted.rs @@ -1,8 +1,8 @@ use core::mem::replace; use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; -use crate::private::Entry; use crate::iterator::*; +use crate::private::Entry; /// Alias of [`ArrayLength`](../generic_array/trait.ArrayLength.html) pub trait Size: ArrayLength> {} @@ -147,4 +147,4 @@ where _ => None, } } -} \ No newline at end of file +} 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() { From 8cc23c99fe7233a86cf2a2bf2d62dabae74636e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 10:43:02 +0200 Subject: [PATCH 05/16] Move implementation to its place --- src/iterator.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/iterator.rs b/src/iterator.rs index 19f4fd7..4f5405a 100644 --- a/src/iterator.rs +++ b/src/iterator.rs @@ -13,19 +13,6 @@ impl<'a, IT> Iter<'a, IT> { } } -/// 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 fn from_iter(inner: &'a mut [Entry]) -> Self { - Self { - inner: inner.iter_mut(), - } - } -} - impl<'a, IT> Iterator for Iter<'a, IT> { type Item = &'a IT; @@ -39,6 +26,19 @@ impl<'a, IT> Iterator for Iter<'a, IT> { } } +/// 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 fn from_iter(inner: &'a mut [Entry]) -> Self { + Self { + inner: inner.iter_mut(), + } + } +} + impl<'a, IT> Iterator for IterMut<'a, IT> { type Item = &'a mut IT; From f849b2e4c3c0d76c6378921a5c5a669ad148dc38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 11:11:03 +0200 Subject: [PATCH 06/16] Some doc improvements --- src/lib.rs | 134 ++------------------------------------ src/slots.rs | 153 +++++++++++++++++++++++++++++++++++++++++++- src/unrestricted.rs | 34 ++++++++-- 3 files changed, 184 insertions(+), 137 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a5f346d..cef1527 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,137 +1,11 @@ -//! This crate provides a heapless slab allocator with strict access control. +//! This crate provides a heapless slab allocator. //! //! Slots implements a "heapless", fixed size, unordered data structure, //! inspired by SlotMap. //! -//! # 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(); -//! -//! // 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`]: ./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)] diff --git a/src/slots.rs b/src/slots.rs index 55c5c02..d0292a8 100644 --- a/src/slots.rs +++ b/src/slots.rs @@ -1,3 +1,138 @@ +//! 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(); +//! +//! // 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; @@ -37,9 +172,13 @@ impl Key { } } -/// 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. +/// 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 @@ -98,7 +237,15 @@ where self.inner.count() } + /// Returns whether all the slots are occupied and the next store() will fail. + 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 ownership 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)) } diff --git a/src/unrestricted.rs b/src/unrestricted.rs index 208b099..364b0ce 100644 --- a/src/unrestricted.rs +++ b/src/unrestricted.rs @@ -1,3 +1,14 @@ +//! 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. +//! +//! As opposed to [`Slots`](../slots/index.html), 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). + use core::mem::replace; use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; @@ -8,6 +19,13 @@ use crate::private::Entry; 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) pub struct UnrestrictedSlots where N: Size, @@ -30,7 +48,7 @@ impl UnrestrictedSlots where N: Size, { - /// Creates a new, empty Slots object. + /// Creates a new, empty UnrestrictedSlots object. pub fn new() -> Self { Self { items: GenericArray::generate(|i| { @@ -51,6 +69,10 @@ where Iter::from_iter(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. pub fn iter_mut(&mut self) -> IterMut { IterMut::from_iter(self.items.as_mut_slice()) } @@ -65,14 +87,15 @@ where self.count } - fn full(&self) -> bool { + /// Returns whether all the slots are occupied and the next store() will fail. + 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.full() { + self.items[idx] = if self.is_full() { Entry::EmptyLast } else { Entry::EmptyNext(self.next_free) @@ -83,7 +106,7 @@ where } fn alloc(&mut self) -> Option { - if self.full() { + if self.is_full() { // no free slot None } else { @@ -101,6 +124,9 @@ where } /// 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 ownership is returned in the return value. pub fn store(&mut self, item: IT) -> Result { match self.alloc() { Some(i) => { From 7f87b096fbcaafabf5439b79ebaba604e60ec863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 12:48:05 +0200 Subject: [PATCH 07/16] Clean up Default impl --- src/private.rs | 6 ++++++ src/unrestricted.rs | 10 +--------- 2 files changed, 7 insertions(+), 9 deletions(-) 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/unrestricted.rs b/src/unrestricted.rs index 364b0ce..62a4c52 100644 --- a/src/unrestricted.rs +++ b/src/unrestricted.rs @@ -26,6 +26,7 @@ impl Size for T where T: ArrayLength> {} /// - `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, @@ -35,15 +36,6 @@ where count: usize, } -impl Default for UnrestrictedSlots -where - N: Size, -{ - fn default() -> Self { - Self::new() - } -} - impl UnrestrictedSlots where N: Size, From 5524dc592f25fa8b574f598a53d0326e6a38d0bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 13:00:29 +0200 Subject: [PATCH 08/16] Add is_full to basic doc test --- src/slots.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slots.rs b/src/slots.rs index d0292a8..6afc645 100644 --- a/src/slots.rs +++ b/src/slots.rs @@ -23,6 +23,8 @@ //! 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); From 19dfc43ab21d20ab470d5359c0cf0c0e3e549652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 13:05:38 +0200 Subject: [PATCH 09/16] Improve crate-level description --- src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cef1527..64fcc34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ -//! This crate provides a heapless slab allocator. +//! 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: +//! - Insert: 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 //! //! 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 +//! * [`UnrestrictedSlots`](./unrestricted/index.html), where elements are free to be modified by anyone #![cfg_attr(not(test), no_std)] From 9ce459e71f9c3e2f294af3c00bcc2867b117162a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 13:07:42 +0200 Subject: [PATCH 10/16] Add example to unrestricted --- src/unrestricted.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/unrestricted.rs b/src/unrestricted.rs index 62a4c52..118e295 100644 --- a/src/unrestricted.rs +++ b/src/unrestricted.rs @@ -3,11 +3,42 @@ //! Data type that stores values and returns an index that can be used to manipulate //! the stored values. //! -//! As opposed to [`Slots`](../slots/index.html), it's not guaranteed that the accessed slot has valid data. +//! 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}; From fa3ec5f44582abc282b5305fc93d436da9158170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 13:32:22 +0200 Subject: [PATCH 11/16] Add some trivial examples --- src/slots.rs | 39 ++++++++++++++++++++++++++++++++++++++- src/unrestricted.rs | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/slots.rs b/src/slots.rs index 6afc645..e8971fd 100644 --- a/src/slots.rs +++ b/src/slots.rs @@ -230,16 +230,53 @@ where 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() will fail. + /// 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() } diff --git a/src/unrestricted.rs b/src/unrestricted.rs index 118e295..48fe4d5 100644 --- a/src/unrestricted.rs +++ b/src/unrestricted.rs @@ -101,16 +101,54 @@ where } /// 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() will fail. + + /// 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() } From 66f549b374f854c161080a91d62c746ea7d03c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 13:47:45 +0200 Subject: [PATCH 12/16] Fix op name --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 64fcc34..baa54c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! This crate provides a heapless, fixed size, unordered data structure, inspired by SlotMap. //! //! The following basic operations (all of them `O(1)`) are defined for Slots: -//! - Insert: store data and retrieve a handle for later access +//! - 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 //! From f219df9d9735ca02b0c3361b91050a239ea3a3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 13:52:54 +0200 Subject: [PATCH 13/16] Iterator examples --- src/slots.rs | 13 ++++++++++++- src/unrestricted.rs | 29 +++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/slots.rs b/src/slots.rs index e8971fd..26ad099 100644 --- a/src/slots.rs +++ b/src/slots.rs @@ -217,6 +217,17 @@ where /// 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() } @@ -284,7 +295,7 @@ where /// 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 ownership is returned in the return value. + /// 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)) } diff --git a/src/unrestricted.rs b/src/unrestricted.rs index 48fe4d5..1a14232 100644 --- a/src/unrestricted.rs +++ b/src/unrestricted.rs @@ -88,6 +88,17 @@ where /// 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_iter(self.items.as_slice()) } @@ -96,6 +107,21 @@ where /// 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_iter(self.items.as_mut_slice()) } @@ -131,7 +157,6 @@ where self.count } - /// Returns whether all the slots are occupied and the next [`store()`](#method.store) will fail. /// /// ``` @@ -187,7 +212,7 @@ where /// 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 ownership is returned in the return value. + /// the inserted data is returned in the return value. pub fn store(&mut self, item: IT) -> Result { match self.alloc() { Some(i) => { From 1777f5a871eb0ce410ac211a22b209ca14ee7f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 13:56:30 +0200 Subject: [PATCH 14/16] Rename and hide iterator constructors --- src/iterator.rs | 4 ++-- src/unrestricted.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/iterator.rs b/src/iterator.rs index 4f5405a..0aea1fb 100644 --- a/src/iterator.rs +++ b/src/iterator.rs @@ -6,7 +6,7 @@ pub struct Iter<'a, IT> { } impl<'a, IT> Iter<'a, IT> { - pub fn from_iter(inner: &'a [Entry]) -> Self { + pub(crate) fn from_entry_slice(inner: &'a [Entry]) -> Self { Self { inner: inner.into_iter(), } @@ -32,7 +32,7 @@ pub struct IterMut<'a, IT> { } impl<'a, IT> IterMut<'a, IT> { - pub fn from_iter(inner: &'a mut [Entry]) -> Self { + pub(crate) fn from_entry_slice(inner: &'a mut [Entry]) -> Self { Self { inner: inner.iter_mut(), } diff --git a/src/unrestricted.rs b/src/unrestricted.rs index 1a14232..71977a8 100644 --- a/src/unrestricted.rs +++ b/src/unrestricted.rs @@ -100,7 +100,7 @@ where /// assert_eq!(true, slots.iter().any(|&x| x < 3)); /// ``` pub fn iter(&self) -> Iter { - Iter::from_iter(self.items.as_slice()) + Iter::from_entry_slice(self.items.as_slice()) } /// Returns a read-write iterator. @@ -123,7 +123,7 @@ where /// assert_eq!(4, slots.take(k).unwrap()); /// ``` pub fn iter_mut(&mut self) -> IterMut { - IterMut::from_iter(self.items.as_mut_slice()) + IterMut::from_entry_slice(self.items.as_mut_slice()) } /// Returns the number of slots From 09d5632f70df2975178303a47c6b8bc28f9f45b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 15:06:10 +0200 Subject: [PATCH 15/16] Add more examples --- src/slots.rs | 54 +++++++++++++++++++++++++++++++++++++++++++-- src/unrestricted.rs | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/slots.rs b/src/slots.rs index 26ad099..19851bc 100644 --- a/src/slots.rs +++ b/src/slots.rs @@ -311,6 +311,18 @@ where /// /// 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); @@ -322,14 +334,52 @@ where /// /// 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 { - self.inner.read(key, function) + /// + /// 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); diff --git a/src/unrestricted.rs b/src/unrestricted.rs index 71977a8..81986c2 100644 --- a/src/unrestricted.rs +++ b/src/unrestricted.rs @@ -224,6 +224,8 @@ where } /// 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); @@ -238,6 +240,26 @@ where /// /// 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 @@ -253,6 +275,31 @@ where /// /// 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)), From d99eda350d43e049d3381c38231cc3483a698d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 21 May 2020 15:11:46 +0200 Subject: [PATCH 16/16] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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]