diff --git a/CHANGELOG.md b/CHANGELOG.md index 0637d20..39e4b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ Unreleased ========== +* Add Relaxed access mode to allow copyable keys and failable access [@bugadani](https://github.com/bugadani) * Verify that keys are used to access data in their associated Slots instances [@bugadani](https://github.com/bugadani) * Allow compiler to reduce memory footprint if possible [@chrysn](https://github.com/chrysn) * Allow using FnOnce closures in `try_read`, `read` and `modify` [@chrysn](https://github.com/chrysn) diff --git a/src/lib.rs b/src/lib.rs index 494f906..77936de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,10 @@ //! Example usage: //! //! ``` -//! use slots::Slots; +//! use slots::{Slots, Strict}; //! use slots::consts::U6; //! -//! let mut slots: Slots<_, U6> = Slots::new(); // Capacity of 6 elements +//! let mut slots: Slots<_, U6, Strict> = Slots::new(); // Capacity of 6 elements //! //! // Store elements //! let k1 = slots.store(2).unwrap(); // returns Err(2) if full @@ -50,8 +50,6 @@ #![cfg_attr(not(test), no_std)] use core::marker::PhantomData; -#[cfg(feature = "verify_owner")] -use core::sync::atomic::{AtomicUsize, Ordering}; use generic_array::{GenericArray, sequence::GenericSequence}; pub use generic_array::typenum::consts; @@ -59,9 +57,50 @@ pub use generic_array::ArrayLength; use generic_array::typenum::Unsigned; -pub struct Key { - #[cfg(feature = "verify_owner")] - owner_id: usize, +mod access_mode { + pub struct Relaxed {} + pub struct Strict {} + + pub trait AccessMode { + type ObjectId; + + fn new_obj_id() -> Self::ObjectId; + } + + impl AccessMode for Relaxed { + type ObjectId = (); + + fn new_obj_id() -> Self::ObjectId { + () + } + } + impl AccessMode for Strict { + #[cfg(feature = "verify_owner")] + type ObjectId = usize; + + #[cfg(not(feature = "verify_owner"))] + type ObjectId = (); + + #[cfg(feature = "verify_owner")] + fn new_obj_id() -> Self::ObjectId { + use core::sync::atomic::{AtomicUsize, Ordering}; + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + COUNTER.fetch_add(1, Ordering::Relaxed) + } + + #[cfg(not(feature = "verify_owner"))] + fn new_obj_id() -> Self::ObjectId { + () + } + } +} + +pub use access_mode::{Strict, Relaxed}; + +pub struct Key + where N: ArrayLength> + Unsigned { + owner_id: ::ObjectId, index: usize, _item_marker: PhantomData, _size_marker: PhantomData @@ -69,9 +108,8 @@ pub struct Key { impl Key where N: ArrayLength> + Unsigned { - fn new(owner: &Slots, idx: usize) -> Self { + fn new(owner: &Slots, idx: usize) -> Self { Self { - #[cfg(feature = "verify_owner")] owner_id: owner.id, index: idx, _item_marker: PhantomData, @@ -95,53 +133,38 @@ 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 - where N: ArrayLength> + Unsigned { - #[cfg(feature = "verify_owner")] - id: usize, +pub struct Slots + where N: ArrayLength> + Unsigned, + A: access_mode::AccessMode { + id: A::ObjectId, items: GenericArray, N>, - // Could be optimized by making it just usize and relying on free_count to determine its - // validity + // Could be optimized by making it just usize and relying on count to determine its validity next_free: Option, - free_count: usize + count: usize, + _mode_marker: PhantomData } -#[cfg(feature = "verify_owner")] -fn new_instance_id() -> usize { - static COUNTER: AtomicUsize = AtomicUsize::new(0); - - COUNTER.fetch_add(1, Ordering::Relaxed) -} - -impl Slots - where N: ArrayLength> + Unsigned { +impl Slots + where N: ArrayLength> + Unsigned, + A: access_mode::AccessMode { pub fn new() -> Self { let size = N::to_usize(); Self { - #[cfg(feature = "verify_owner")] - id: new_instance_id(), + id: A::new_obj_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 + count: 0, + _mode_marker: PhantomData } } - #[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() } pub fn count(&self) -> usize { - self.capacity() - self.free_count + self.count } fn free(&mut self, idx: usize) { @@ -150,7 +173,7 @@ impl Slots None => Entry(EntryInner::EmptyLast), }; self.next_free = Some(idx); - self.free_count += 1; + self.count -= 1; } fn alloc(&mut self) -> Option { @@ -160,53 +183,97 @@ impl Slots EntryInner::EmptyLast => None, _ => unreachable!("Non-empty item in entry behind free chain"), }; - self.free_count -= 1; + self.count += 1; Some(index) } - pub fn store(&mut self, item: IT) -> Result, IT> { + 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); + fn _remove(&mut self, idx: usize) -> Option { + let taken = core::mem::replace(&mut self.items[idx], Entry(EntryInner::EmptyLast)); - let taken = core::mem::replace(&mut self.items[key.index], Entry(EntryInner::EmptyLast)); - self.free(key.index); match taken.0 { - EntryInner::Used(item) => item, - _ => unreachable!("Invalid key") + EntryInner::Used(item) => { + self.free(idx); + 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") + fn _read(&self, idx: usize, function: F) -> Option where F: FnOnce(&IT) -> T { + match &self.items[idx].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)), + fn _modify(&mut self, idx: usize, function: F) -> Option where F: FnOnce(&mut IT) -> T { + match self.items[idx].0 { + EntryInner::Used(ref mut item) => Some(function(item)), _ => None } } +} + +impl Slots + where N: ArrayLength> + Unsigned { + + fn verify_key(&self, key: &Key) { + assert!(key.owner_id == self.id, "Key used in wrong instance"); + } + + pub fn store(&mut self, item: IT) -> Result, IT> { + self._store(item).map(|i| Key::new(self, i)) + } + + pub fn take(&mut self, key: Key) -> IT { + self.verify_key(&key); + + self._remove(key.index).expect("Invalid key") + } + + pub fn read(&self, key: &Key, function: F) -> T where F: FnOnce(&IT) -> T { + self.verify_key(&key); + + self._read(key.index, function).expect("Invalid key") + } + + pub fn try_read(&self, idx: usize, function: F) -> Option where F: FnOnce(&IT) -> T { + self._read(idx, function) + } pub fn modify(&mut self, key: &Key, function: F) -> T where F: FnOnce(&mut IT) -> T { self.verify_key(&key); - match self.items[key.index].0 { - EntryInner::Used(ref mut item) => function(item), - _ => unreachable!("Invalid key") - } + self._modify(key.index, function).expect("Invalid key") + } +} + +impl Slots + where N: ArrayLength> + Unsigned { + + pub fn store(&mut self, item: IT) -> Result { + self._store(item) + } + + pub fn take(&mut self, idx: usize) -> Option { + self._remove(idx) + } + + pub fn modify(&mut self, idx: usize, function: F) -> Option where F: FnOnce(&mut IT) -> T { + self._modify(idx, function) + } + + pub fn read(&self, idx: usize, function: F) -> Option where F: FnOnce(&IT) -> T { + self._read(idx, function) } } diff --git a/tests/test.rs b/tests/test.rs index aba69f6..5a60999 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,9 +1,10 @@ -use slots::Slots; +use slots::{Slots, Strict, Relaxed}; use slots::consts::*; +use core::mem::size_of; #[test] fn key_can_be_used_to_read_value() { - let mut slots: Slots<_, U8> = Slots::new(); + let mut slots: Slots<_, U8, Strict> = Slots::new(); let k1 = slots.store(5).unwrap(); assert_eq!(5, slots.read(&k1, |&w| w)); @@ -11,7 +12,7 @@ fn key_can_be_used_to_read_value() { #[test] fn size_can_be_1() { - let mut slots: Slots<_, U1> = Slots::new(); + let mut slots: Slots<_, U1, Strict> = Slots::new(); let k1 = slots.store(5).unwrap(); assert_eq!(5, slots.read(&k1, |&w| w)); @@ -27,7 +28,7 @@ fn size_can_be_1() { #[test] fn index_can_be_used_to_read_value() { - let mut slots: Slots<_, U8> = Slots::new(); + let mut slots: Slots<_, U8, Strict> = Slots::new(); slots.store(5).unwrap(); slots.store(6).unwrap(); @@ -40,14 +41,14 @@ fn index_can_be_used_to_read_value() { #[test] fn trying_to_read_missing_element_returns_none() { - let slots: Slots = Slots::new(); + let slots: Slots = Slots::new(); assert_eq!(None, slots.try_read(0, |&w| w)); } #[test] fn trying_to_read_deleted_element_returns_none() { - let mut slots: Slots = Slots::new(); + let mut slots: Slots = Slots::new(); slots.store(5).unwrap(); let k = slots.store(6).unwrap(); @@ -62,7 +63,7 @@ fn trying_to_read_deleted_element_returns_none() { #[test] fn elements_can_be_modified_using_key() { - let mut slots: Slots = Slots::new(); + let mut slots: Slots = Slots::new(); let k = slots.store(5).unwrap(); @@ -75,7 +76,7 @@ fn elements_can_be_modified_using_key() { #[test] fn store_returns_err_when_full() { - let mut slots: Slots = Slots::new(); + let mut slots: Slots = Slots::new(); slots.store(5); @@ -88,8 +89,8 @@ fn store_returns_err_when_full() { #[cfg(feature = "verify_owner")] #[should_panic(expected = "Key used in wrong instance")] fn use_across_slots_verify() { - let mut a: Slots = Slots::new(); - let mut b: Slots = Slots::new(); + let mut a: Slots = Slots::new(); + let mut b: Slots = Slots::new(); let k = a.store(5).expect("There should be room"); // Store an element in b so we don't get a different panic @@ -101,8 +102,8 @@ fn use_across_slots_verify() { #[test] #[cfg(not(feature = "verify_owner"))] fn use_across_slots_no_verify() { - let mut a: Slots = Slots::new(); - let mut b: Slots = Slots::new(); + let mut a: Slots = Slots::new(); + let mut b: Slots = Slots::new(); let k = a.store(5).expect("There should be room"); // Store an element in b so we don't get a different panic @@ -132,18 +133,18 @@ fn is_compact() { b: bool, } - assert_eq!(core::mem::size_of::(), 16); + assert_eq!(size_of::(), 16); - let mut expected_size = 32 * 16 + 3 * core::mem::size_of::(); + let mut expected_size = 32 * 16 + 3 * size_of::(); if cfg!(feature = "verify_owner") { - expected_size += core::mem::size_of::(); // an extra usize for object id + expected_size += size_of::(); // an extra usize for object id } - assert_eq!(core::mem::size_of::>(), expected_size, "Compiled size does not match expected"); + assert_eq!(size_of::>(), expected_size, "Compiled size does not match expected"); } #[test] fn capacity_and_count() { - let mut slots: Slots = Slots::new(); + let mut slots: Slots = Slots::new(); assert_eq!(slots.capacity(), 4); assert_eq!(slots.count(), 0); @@ -165,3 +166,12 @@ fn capacity_and_count() { assert_eq!(slots.count(), 0); } + +#[test] +fn relaxed_instance_does_not_have_object_id() { + if cfg!(feature = "verify_owner") { + assert_eq!(size_of::>(), size_of::>() + size_of::()); + } else { + assert_eq!(size_of::>(), size_of::>()); + } +}