From 426815e9aa9759a42a14ef7cf2041f5379fb107a Mon Sep 17 00:00:00 2001 From: chrysn Date: Wed, 29 Apr 2020 09:40:37 +0200 Subject: [PATCH 1/2] Split RawSlots and Slots RawSlots operate purely on unprotecetd indices, Slots gives all the protection around it. --- src/lib.rs | 109 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 494f906..dbe577a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,10 +95,8 @@ enum EntryInner { // 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 +pub struct RawSlots where N: ArrayLength> + Unsigned { - #[cfg(feature = "verify_owner")] - id: usize, items: GenericArray, N>, // Could be optimized by making it just usize and relying on free_count to determine its // validity @@ -106,6 +104,13 @@ pub struct Slots free_count: usize } +pub struct Slots +where N: ArrayLength> + Unsigned { + #[cfg(feature = "verify_owner")] + id: usize, + raw: RawSlots +} + #[cfg(feature = "verify_owner")] fn new_instance_id() -> usize { static COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -113,29 +118,18 @@ fn new_instance_id() -> usize { COUNTER.fetch_add(1, Ordering::Relaxed) } -impl Slots +impl RawSlots where N: ArrayLength> + Unsigned { pub fn new() -> Self { let size = N::to_usize(); Self { - #[cfg(feature = "verify_owner")] - id: new_instance_id(), items: GenericArray::generate(|i| Entry(i.checked_sub(1).map(EntryInner::EmptyNext).unwrap_or(EntryInner::EmptyLast))), next_free: size.checked_sub(1), free_count: size } } - #[cfg(feature = "verify_owner")] - fn verify_key(&self, key: &Key) { - assert!(key.owner_id == self.id, "Key used in wrong instance"); - } - - #[cfg(not(feature = "verify_owner"))] - fn verify_key(&self, _key: &Key) { - } - pub fn capacity(&self) -> usize { N::to_usize() } @@ -164,49 +158,88 @@ impl Slots Some(index) } - 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(EntryInner::Used(item)); - Ok(Key::new(self, i)) + Ok(i) } None => Err(item) } } - pub fn take(&mut self, key: Key) -> IT { - self.verify_key(&key); - - let taken = core::mem::replace(&mut self.items[key.index], Entry(EntryInner::EmptyLast)); - self.free(key.index); + pub fn take(&mut self, key: usize) -> Option { + let taken = core::mem::replace(&mut self.items[key], Entry(EntryInner::EmptyLast)); + self.free(key); match taken.0 { - EntryInner::Used(item) => item, - _ => unreachable!("Invalid key") + EntryInner::Used(item) => Some(item), + _ => None } } - pub fn read(&self, key: &Key, function: F) -> T where F: FnOnce(&IT) -> T { - self.verify_key(&key); - - match self.try_read(key.index, function) { - Some(t) => t, - None => unreachable!("Invalid key") + pub fn read(&self, key: usize, function: F) -> Option where F: FnOnce(&IT) -> T { + match &self.items[key].0 { + EntryInner::Used(item) => Some(function(&item)), + _ => None } } - pub fn try_read(&self, key: usize, function: F) -> Option where F: FnOnce(&IT) -> T { - match &self.items[key].0 { - EntryInner::Used(item) => Some(function(&item)), + pub fn modify(&mut self, key: usize, function: F) -> Option where F: FnOnce(&mut IT) -> T { + match self.items[key].0 { + EntryInner::Used(ref mut item) => Some(function(item)), _ => None } } +} - pub fn modify(&mut self, key: &Key, function: F) -> T where F: FnOnce(&mut IT) -> T { - self.verify_key(&key); +impl Slots + where N: ArrayLength> + Unsigned { - match self.items[key.index].0 { - EntryInner::Used(ref mut item) => function(item), - _ => unreachable!("Invalid key") + pub fn new() -> Self { + Self { + #[cfg(feature = "verify_owner")] + id: new_instance_id(), + raw: RawSlots::new(), } } + + #[cfg(feature = "verify_owner")] + fn verify_key(&self, key: &Key) { + assert!(key.owner_id == self.id, "Key used in wrong instance"); + } + + #[cfg(not(feature = "verify_owner"))] + fn verify_key(&self, _key: &Key) { + } + + pub fn capacity(&self) -> usize { + self.raw.capacity() + } + + pub fn count(&self) -> usize { + self.raw.count() + } + + pub fn store(&mut self, item: IT) -> Result, IT> { + self.raw.store(item).map(|id| Key::new(self, id)) + } + + pub fn take(&mut self, key: Key) -> IT { + self.verify_key(&key); + self.raw.take(key.index()).expect("Invalid key") + } + + pub fn read(&self, key: &Key, function: F) -> T where F: FnOnce(&IT) -> T { + self.verify_key(&key); + self.raw.read(key.index(), function).expect("Invalid key") + } + + pub fn try_read(&self, key: usize, function: F) -> Option where F: FnOnce(&IT) -> T { + self.raw.read(key, function) + } + + pub fn modify(&mut self, key: &Key, function: F) -> T where F: FnOnce(&mut IT) -> T { + self.verify_key(&key); + self.raw.modify(key.index(), function).expect("Invalid key") + } } From ec603ebce60e185afa2719ce9eec9b30c9987376 Mon Sep 17 00:00:00 2001 From: chrysn Date: Thu, 30 Apr 2020 14:43:53 +0200 Subject: [PATCH 2/2] Add rustdoc documentation --- src/lib.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dbe577a..ed0eacd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,16 @@ pub use generic_array::ArrayLength; use generic_array::typenum::Unsigned; +/// Access key to a Slots instance +/// +/// Keys must only be used with the Slots they were generated from; using them for any other access +/// is a logic error and results in immediate or later panics from the Slot's functions. Erroneous +/// use is largely caught by type constraints; additional runtime constraints can be enabled using +/// the `verify_owner` feature. +/// +/// Unless `verify_owner` is used, a Key is pointer-sized. +/// +/// Keys can only be created by a Slot's [`store`](struct.Slots.html#method.store) method. pub struct Key { #[cfg(feature = "verify_owner")] owner_id: usize, @@ -79,11 +89,31 @@ impl Key } } + /// The underlying index of the key + /// + /// A key's index can be used in fallible access using the + /// [`try_read()`](struct.Slots.html#method.try_read) method. pub fn index(&self) -> usize { self.index } } +/// Internal element of `RawSlots` instances +/// +/// This struct is not expected to be used by code outside the slots crate, except to define +/// suitable array sizes that are `ArrayLength>` as required by the +/// [`RawSlots`](struct.RawSlots.html) and [`Slots`](struct.Slots.html) generics +/// for usages that are generic over the length of the used slots: +/// +/// ``` +/// # use slots::*; +/// fn examine(slots: &Slots, keys: &[Key]) +/// where +/// N: slots::ArrayLength>, +/// { +/// unimplemented!(); +/// } +/// ``` pub struct Entry(EntryInner); enum EntryInner { @@ -92,9 +122,15 @@ enum EntryInner { EmptyLast } -// 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. +/// An unchecked slab allocator with predetermined size +/// +/// The allocator deals out usize handles that can be used to access the data later through a +/// (shared or mutable) reference to the `RawSlots`. All access is fallible, as the handles can be +/// arbitrarily created. +/// +/// It is up to slots' users to ensure that the item they intend to access is still identified by +/// that handle, especially as it can have been removed in the meantime, and the handle replaced by +/// a different object. pub struct RawSlots where N: ArrayLength> + Unsigned { items: GenericArray, N>, @@ -104,6 +140,12 @@ pub struct RawSlots free_count: usize } +/// A type-checked slab allocator with predetermined size +/// +/// The allocator deals out [`Key`](struct.Key.html) objects that can be used to access the data +/// later through a (shared or mutable) reference to the Slots. By the interface's design, access +/// is guaranteed to succeed, as the keys are unclonable and consumed to remove an item from the +/// Slots instance. pub struct Slots where N: ArrayLength> + Unsigned { #[cfg(feature = "verify_owner")] @@ -120,6 +162,7 @@ fn new_instance_id() -> usize { impl RawSlots where N: ArrayLength> + Unsigned { + /// Create an empty raw slot allocator of size `N`. pub fn new() -> Self { let size = N::to_usize(); @@ -158,6 +201,10 @@ impl RawSlots Some(index) } + /// Put an item into a free slot + /// + /// This returns an access index for the stored data in the success case, or hands the unstored + /// item back in case of an error. pub fn store(&mut self, item: IT) -> Result { match self.alloc() { Some(i) => { @@ -168,24 +215,31 @@ impl RawSlots } } - pub fn take(&mut self, key: usize) -> Option { - let taken = core::mem::replace(&mut self.items[key], Entry(EntryInner::EmptyLast)); - self.free(key); + /// Move an item out of a slot, if it exists + pub fn take(&mut self, index: usize) -> Option { + let taken = core::mem::replace(&mut self.items[index], Entry(EntryInner::EmptyLast)); + self.free(index); match taken.0 { EntryInner::Used(item) => Some(item), _ => None } } - pub fn read(&self, key: usize, function: F) -> Option where F: FnOnce(&IT) -> T { - match &self.items[key].0 { + /// Provide immutable access to an item + /// + /// The callback is only run if the given index is currently valid. + pub fn read(&self, index: usize, function: F) -> Option where F: FnOnce(&IT) -> T { + match &self.items[index].0 { EntryInner::Used(item) => Some(function(&item)), _ => None } } - pub fn modify(&mut self, key: usize, function: F) -> Option where F: FnOnce(&mut IT) -> T { - match self.items[key].0 { + /// Provide mutable access to an item + // + /// The callback is only run if the given index is currently valid. + pub fn modify(&mut self, index: usize, function: F) -> Option where F: FnOnce(&mut IT) -> T { + match self.items[index].0 { EntryInner::Used(ref mut item) => Some(function(item)), _ => None } @@ -195,6 +249,10 @@ impl RawSlots impl Slots where N: ArrayLength> + Unsigned { + /// Create an empty slot allocator of size `N`. + /// + /// If the `verify_owner` feature is enabled, it will carry a new unique (except for + /// wraparounds) ID which it shares with its keys. pub fn new() -> Self { Self { #[cfg(feature = "verify_owner")] @@ -220,24 +278,37 @@ impl Slots self.raw.count() } + /// Put an item into a free slot + /// + /// This returns an access index for the stored data in the success case, or hands the unstored + /// item back in case of an error. pub fn store(&mut self, item: IT) -> Result, IT> { self.raw.store(item).map(|id| Key::new(self, id)) } + /// Move an item out of its slot pub fn take(&mut self, key: Key) -> IT { self.verify_key(&key); self.raw.take(key.index()).expect("Invalid key") } + /// Provide immutable access to an item pub fn read(&self, key: &Key, function: F) -> T where F: FnOnce(&IT) -> T { self.verify_key(&key); self.raw.read(key.index(), function).expect("Invalid key") } - pub fn try_read(&self, key: usize, function: F) -> Option where F: FnOnce(&IT) -> T { - self.raw.read(key, function) + /// Opportunistic immutable access to an item by its index + /// + /// A suitable index can be generated from a [`Key`](struct.Key.html) through its + /// [`index()`](struct.Key.html#method.index) method; unlike the regular access, this can fail if + /// the element has been removed (in which case the function is not run at all), or might have + /// been replaced by a completely unrelated element inbetween. + pub fn try_read(&self, index: usize, function: F) -> Option where F: FnOnce(&IT) -> T { + self.raw.read(index, function) } + /// Provide mutable access to an item pub fn modify(&mut self, key: &Key, function: F) -> T where F: FnOnce(&mut IT) -> T { self.verify_key(&key); self.raw.modify(key.index(), function).expect("Invalid key")