diff --git a/CHANGELOG.md b/CHANGELOG.md index fc7c4df..070ca8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.1.2 (Dec 18, 2025) + +* Fix bug in `StrongSlab` +* Update documentation + # 0.1.1 (March 08, 2024) * Update documentation diff --git a/Cargo.toml b/Cargo.toml index 1100a8b..6c87bd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,5 @@ [package] - name = "generic_slab" -# When releasing to crates.io: -# - Update version number -# - README.md -# - Update CHANGELOG.md -# - Create git tag version = "0.1.1" authors = [ "Bergmann89 ", "Carl Lerche " ] edition = "2018" @@ -17,6 +11,9 @@ keywords = [ "slab", "allocator", "no_std", "fixed", "generic" ] categories = [ "memory-management", "data-structures", "no-std" ] exclude = [ "/.*" ] +[package.metadata.docs.rs] +all-features = true + [features] std = [ ] default = [ "std" ] @@ -33,3 +30,33 @@ arrayvec = "0.7.4" rustversion = "1" serde = { version = "1", features = [ "derive" ] } serde_test = "1" + + +[lints.rust] +future_incompatible = { level = "warn", priority = -1} +incomplete_features = "allow" +missing_debug_implementations = { level = "warn", priority = -1} +missing_docs = { level = "warn", priority = -1} +nonstandard_style = { level = "warn", priority = -1} +rust_2018_idioms = { level = "warn", priority = -1} +rust_2021_compatibility = { level = "warn", priority = -1} +unexpected_cfgs = "allow" +unreachable_pub = { level = "warn", priority = -1} +unused = { level = "warn", priority = -1} + +[lints.clippy] +cast_possible_truncation = "warn" +default_trait_access = "allow" +explicit_iter_loop = "allow" +match_same_arms = "allow" +match_wildcard_for_single_variants = "allow" +missing_fields_in_debug = "allow" +missing_panics_doc = "allow" +module_name_repetitions = "allow" +needless_pass_by_value = "allow" +no_effect_underscore_binding = "allow" +pedantic = { level = "warn", priority = -1} +similar_names = "allow" +single_match_else = "allow" +struct_field_names = "allow" +uninlined_format_args = "allow" diff --git a/README.md b/README.md index eaa7b76..aeddd04 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,7 @@ `generic_slab` is a fork of [`slab`](https://github.com/tokio-rs/slab) that provides more control over the key and storage types. Using a generic approach you can for example implement strong typed keys that must fit the data type stored in the slab, or you can use a fixed size array as backing storage for the slab data. -[![Crates.io][crates-badge]][crates-url] -[![Build Status][ci-badge]][ci-url] - -[crates-badge]: https://img.shields.io/crates/v/generic_slab -[crates-url]: https://crates.io/crates/generic_slab -[ci-badge]: https://img.shields.io/github/actions/workflow/status/Bergmann89/generic_slab/ci.yml?branch=master -[ci-url]: https://github.com/Bergmann89/generic_slab/actions +Crates.io License Crates.io Version Crates.io Total Downloads docs.rs Github CI Dependency Status [Documentation](https://docs.rs/generic_slab) diff --git a/build.rs b/build.rs index b60351a..56d7da8 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,5 @@ +//! Build script that sets some flags depending on the used rust version. + fn main() { let cfg = match autocfg::AutoCfg::new() { Ok(cfg) => cfg, diff --git a/src/entries.rs b/src/entries.rs index 611d1c0..e547444 100644 --- a/src/entries.rs +++ b/src/entries.rs @@ -63,7 +63,7 @@ where #[inline] fn clear(&mut self) { - Vec::clear(self) + Vec::clear(self); } #[inline] diff --git a/src/generic.rs b/src/generic.rs index ed8a4d7..5a89c38 100644 --- a/src/generic.rs +++ b/src/generic.rs @@ -120,6 +120,7 @@ where /// # use generic_slab::*; /// let slab: Slab = Slab::new(); /// ``` + #[must_use] pub fn new() -> Self { Self::from_entries(TEntries::default()) } @@ -130,7 +131,7 @@ where TKey: Key, TEntries: Entries, { - /// Construct a new, empty `Slab` using the provided `entries``. + /// Construct a new, empty `Slab` using the provided `entries`. /// /// Before the slab is created the passed `entries` will be cleared. /// @@ -1522,6 +1523,7 @@ where /// // ...but this may make the slab reallocate /// slab.insert(11); /// ``` + #[must_use] pub fn with_capacity(capacity: usize) -> Self { Self::from_entries(TEntries::with_capacity(capacity)) } @@ -1773,6 +1775,7 @@ where TEntries: Entries, { /// Get the key of this vacant entry. + #[must_use] pub fn key(&self) -> TKey { self.slab.vacant_key_at(self.index) } @@ -1788,7 +1791,7 @@ where } } -impl<'a, T, TKey, TEntries> Debug for GenericVacantEntry<'a, T, TKey, TEntries> +impl Debug for GenericVacantEntry<'_, T, TKey, TEntries> where TKey: Key, { diff --git a/src/handle.rs b/src/handle.rs index a3482a5..21fdd36 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -96,7 +96,7 @@ impl Key for Handle { Self { id: cx.id, index, - count: data.map(Clone::clone).unwrap_or_default(), + count: data.cloned().unwrap_or_default(), marker: PhantomData, } } @@ -115,7 +115,7 @@ impl Key for Handle { fn convert_into_vacant(cx: &Self::Context, data: Self::OccupiedData) -> Self::VacantData { let _cx = cx; - data + data.overflowing_add(1).0 } #[inline] @@ -147,3 +147,23 @@ impl Default for Context { } static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +#[cfg(test)] +mod tests { + use crate::StrongSlab; + + #[test] + fn strong_slab() { + let mut slab = StrongSlab::new(); + + let handle0 = slab.insert("hello"); + assert_eq!(handle0.index, 0); + assert_eq!(handle0.count, 0); + slab.remove(handle0); + + let handle1 = slab.insert("world"); + assert!(slab.get(handle0).is_none()); + assert_eq!(handle1.index, 0); + assert_eq!(handle1.count, 1); + } +} diff --git a/src/lib.rs b/src/lib.rs index a58ffa8..722738a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,28 +10,52 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] -//! Pre-allocated storage for a uniform data type. +//! `generic_slab` is a fork of [`slab`](https://github.com/tokio-rs/slab) that +//! keeps the familiar `Slab` API but provides more control over the key and +//! storage types. Using a generic approach you can for example implement strong +//! typed keys that must fit the data type stored in the slab, or you can use a +//! fixed size array as backing storage for the slab data. +//! +//! Crates.io License +//! Crates.io Version +//! Crates.io Total Downloads +//! docs.rs +//! Github CI +//! Dependency Status +//! +//! At its core is [`GenericSlab`], which lets you customize +//! - the **key type** (`TKey: Key`) and +//! - the **backing storage** (`TEntries: Entries`). +//! +//! This enables things like: +//! - strong, type-safe handles that can’t be mixed across element types +//! - fixed-capacity slabs backed by arrays or `ArrayVec` +//! - alternative storage layouts, as long as they implement [`Entries`] +//! +//! ## Differences from the original `slab` crate +//! +//! Compared to [`slab`](https://crates.io/crates/slab): +//! - `generic_slab::Slab` is a **drop-in replacement** for `slab::Slab`: +//! it uses `usize` keys and a `Vec>` behind the scenes. +//! - You can use [`GenericSlab`] directly to plug in a +//! custom key type (anything that implements [`Key`], e.g. [`Handle`]). +//! - You can plug in a custom storage type for entries by implementing +//! [`Entries`] for your own collection type. +//! - The crate provides [`StrongSlab`], which uses [`Handle`] for keys +//! and protects you from accidentally mixing keys from different slabs or +//! different element types. +//! * `generic_slab` is `no_std`-friendly (it works without `std` if you +//! disable the default `std` feature). +//! +//! If you just need the behavior of the original `slab`, use [`Slab`]. +//! If you need more control, use [`GenericSlab`] or +//! [`StrongSlab`]. +//! +//! ## Basic usage (drop-in for `slab`) +//! +//! ```rust +//! use generic_slab::Slab; //! -//! `Slab` provides pre-allocated storage for a single data type. If many values -//! of a single type are being allocated, it can be more efficient to -//! pre-allocate the necessary storage. Since the size of the type is uniform, -//! memory fragmentation can be avoided. Storing, clearing, and lookup -//! operations become very cheap. -//! -//! While `Slab` may look like other Rust collections, it is not intended to be -//! used as a general purpose collection. The primary difference between `Slab` -//! and `Vec` is that `Slab` returns the key when storing the value. -//! -//! It is important to note that keys may be reused. In other words, once a -//! value associated with a given key is removed from a slab, that key may be -//! returned from future calls to `insert`. -//! -//! # Examples -//! -//! Basic storing and retrieval. -//! -//! ``` -//! # use generic_slab::*; //! let mut slab = Slab::new(); //! //! let hello = slab.insert("hello"); @@ -44,128 +68,219 @@ //! assert_eq!(slab[world], "earth"); //! ``` //! -//! Sometimes it is useful to be able to associate the key with the value being -//! inserted in the slab. This can be done with the `vacant_entry` API as such: -//! -//! ``` -//! # use generic_slab::*; -//! let mut slab = Slab::new(); -//! -//! let hello = { -//! let entry = slab.vacant_entry(); -//! let key = entry.key(); -//! -//! entry.insert((key, "hello")); -//! key -//! }; -//! -//! assert_eq!(hello, slab[hello].0); -//! assert_eq!("hello", slab[hello].1); -//! ``` -//! -//! It is generally a good idea to specify the desired capacity of a slab at -//! creation time. Note that `Slab` will grow the internal capacity when -//! attempting to insert a new value once the existing capacity has been reached. -//! To avoid this, add a check. -//! -//! ``` -//! # use generic_slab::*; -//! let mut slab = Slab::with_capacity(1024); -//! -//! // ... use the slab +//! ## Custom Entries Storage (with fixed capacity) //! -//! if slab.len() == slab.capacity() { -//! panic!("slab full"); -//! } -//! -//! slab.insert("the slab is not at capacity yet"); -//! ``` +//! This example shows how to replace the default `Vec>` with a +//! fixed-capacity `ArrayVec`-based storage. //! -//! If you want to you can also use the [`GenericSlab`] to provide custom a -//! implementation for the key and the entries collection. -//! -//! ``` -//! # use generic_slab::*; +//! ```rust //! use arrayvec::ArrayVec; +//! use generic_slab::{GenericSlab, Entries, Entry, Key}; //! //! #[derive(Default)] -//! struct MyEntries(ArrayVec, N>) +//! struct FixedEntries(ArrayVec, N>) //! where -//! TKey: Key; +//! K: Key; //! -//! impl AsRef<[Entry]> for MyEntries +//! impl AsRef<[Entry]> for FixedEntries //! where -//! TKey: Key, +//! K: Key, //! { -//! fn as_ref(&self) -> &[Entry] { -//! &*self.0 +//! fn as_ref(&self) -> &[Entry] { +//! &self.0 //! } //! } //! -//! impl AsMut<[Entry]> for MyEntries +//! impl AsMut<[Entry]> for FixedEntries //! where -//! TKey: Key, +//! K: Key, //! { -//! fn as_mut(&mut self) -> &mut [Entry] { -//! &mut *self.0 +//! fn as_mut(&mut self) -> &mut [Entry] { +//! &mut self.0 //! } //! } //! -//! impl Entries for MyEntries +//! impl Entries for FixedEntries //! where -//! TKey: Key, +//! K: Key, //! { -//! fn capacity(&self) -> usize { -//! N -//! } +//! fn capacity(&self) -> usize { +//! N +//! } //! -//! fn push(&mut self, entry: Entry) { -//! self.0.push(entry); -//! } +//! fn push(&mut self, entry: Entry) { +//! self.0.push(entry); +//! } //! -//! fn pop(&mut self) -> Option> { -//! self.0.pop() -//! } +//! fn pop(&mut self) -> Option> { +//! self.0.pop() +//! } //! -//! fn clear(&mut self) { -//! self.0.clear(); -//! } +//! fn clear(&mut self) { +//! self.0.clear(); +//! } //! } //! -//! type FixedSlab = GenericSlab::>; +//! /// A slab with a fixed maximum of `N` entries, using `usize` keys. +//! type FixedSlab = GenericSlab>; +//! +//! // Use it like a normal slab, but it will never reallocate: +//! let mut slab: FixedSlab<&str, 4> = GenericSlab::new(); +//! let k0 = slab.insert("zero"); +//! let k1 = slab.insert("one"); +//! let k2 = slab.insert("two"); +//! let k3 = slab.insert("three"); +//! +//! assert_eq!(slab[k0], "zero"); +//! assert_eq!(slab[k3], "three"); //! -//! let mut slab = FixedSlab::::new(); -//! let key = slab.insert("test".into()); +//! // Inserting a 5th element would panic here because the ArrayVec is full. //! ``` //! +//! ## Example: Custom Key Type using [`Handle`] +//! +//! This example uses [`StrongSlab`], which is a [`GenericSlab`] configured +//! with [`Handle`] as key type. `Handle` is strongly typed and carries +//! enough metadata to detect stale or cross-slab keys. +//! ```rust +//! use generic_slab::StrongSlab; +//! +//! // Keys are `Handle` instead of `usize`. +//! let mut slab: StrongSlab = StrongSlab::default(); +//! +//! let a = slab.insert("hello".into()); +//! let b = slab.insert("world".into()); +//! +//! assert_eq!(slab[a], "hello"); +//! assert_eq!(slab[b], "world"); +//! +//! // Handles are `Copy` and cheap to pass around. +//! let a2 = a; +//! assert_eq!(slab[a2], "hello"); +//! ``` +//! +//! [`StrongSlab`] and [`Handle`] prevent mixing keys of different element +//! types: +//! +//! ```rust,ignore +//! use generic_slab::StrongSlab; +//! +//! let mut slab: StrongSlab = StrongSlab::default(); +//! let a = slab.insert("hello".into()); +//! +//! let mut ints: StrongSlab = StrongSlab::default(); +//! ints[a]; // <- does not compile: `Handle` is not a key for `StrongSlab`. +//! ``` +//! +//! It also prevents reusing old handles for new data that was inserted at the +//! same place: +//! +//! ```rust +//! use generic_slab::StrongSlab; +//! +//! let mut slab: StrongSlab = StrongSlab::default(); +//! let handle0 = slab.insert("hello".into()); +//! slab.remove(handle0); +//! +//! let handle1 = slab.insert("world".into()); // Will be placed at the same position than the old `handle0`. +//! assert!(slab.get(handle0).is_none()); // But you cant get the new entry from the old handle. +//! assert!(slab.get(handle1).is_some()); +//! ``` +//! +//! For even more control, you can implement [`Key`] for your own key type +//! and plug it into [`GenericSlab`] together with a custom +//! [`Entries`] implementation. +//! +//! ## Range iterators (feature `range`) +//! +//! When the optional `range` feature is enabled, slabs gain efficient +//! range iterators that only visit occupied entries and respect the +//! insertion order. +//! +//! The following APIs are available on [`GenericSlab`] (and therefore on +//! [`Slab`] and [`StrongSlab`]) when `feature = "range"` is enabled: +//! - [`GenericSlab::range`] -> [`RangeIter`] over `(&T)` +//! - [`GenericSlab::range_mut`] -> [`RangeIterMut`] over `(&mut T)` +//! +//! Both methods accept any `R: RangeBounds` (e.g. `key_a..key_b`, +//! `..key_b`, `key_a..`, `..=` etc.). Only valid, occupied keys in the +//! specified range are yielded. If all bounds are invalid, the iterator +//! is empty. +//! +//! ### Iteration order and “reverse” ranges +//! +//! Range iterators always walk the slab in insertion order, starting +//! from the first element that falls into the range and then wrapping +//! around until the upper bound is reached. +//! +//! A “reverse” range like `key_b..key_a` behaves as a *negated* range: +//! it yields all occupied entries that are not in `key_a..key_b`, +//! still in insertion order. +//! +//! ### Example: simple range +//! +//! ```rust +//! # #[cfg(feature = "range")] +//! # { +//! use generic_slab::Slab; +//! +//! let mut slab = Slab::new(); +//! +//! let k0 = slab.insert(0); +//! let k1 = slab.insert(1); +//! let k2 = slab.insert(2); +//! let k3 = slab.insert(3); +//! +//! let mut it = slab.range(k1..k3); +//! +//! assert_eq!(it.next(), Some((k1, &1))); +//! assert_eq!(it.next(), Some((k2, &2))); +//! assert_eq!(it.next(), None); +//! # } +//! ``` +//! +//! ### Example: “negated” range using reversed bounds +//! +//! ```rust +//! # #[cfg(feature = "range")] +//! # { +//! use generic_slab::Slab; +//! +//! let mut slab = Slab::new(); +//! +//! let k0 = slab.insert(0); +//! let k1 = slab.insert(1); +//! let k2 = slab.insert(2); +//! let k3 = slab.insert(3); +//! +//! // `k3..k1` yields everything that is *not* in `k1..k3` +//! let mut it = slab.range(k3..k1); +//! +//! assert_eq!(it.next(), Some((k3, &3))); // after k3, wrap to start +//! assert_eq!(it.next(), Some((k0, &0))); +//! assert_eq!(it.next(), None); +//! # } +//! ``` +//! +//! These iterators are thin wrappers around [`RangeIter`] and +//! [`RangeIterMut`], which you can also name explicitly if you need +//! to store them in a concrete type. +//! //! # Capacity and reallocation //! //! The capacity of a slab is the amount of space allocated for any future //! values that will be inserted in the slab. This is not to be confused with //! the *length* of the slab, which specifies the number of actual values //! currently being inserted. If a slab's length is equal to its capacity, the -//! next value inserted into the slab will require growing the slab by -//! reallocating. -//! -//! For example, a slab with capacity 10 and length 0 would be an empty slab -//! with space for 10 more stored values. Storing 10 or fewer elements into the -//! slab will not change its capacity or cause reallocation to occur. However, -//! if the slab length is increased to 11 (due to another `insert`), it will -//! have to reallocate, which can be slow. For this reason, it is recommended to -//! use [`Slab::with_capacity`] whenever possible to specify how many values the -//! slab is expected to store. +//! next value inserted into the slab will require growing (if the underlying +//! buffer supports it). //! //! # Implementation //! -//! `Slab` is backed by a `Vec` of slots. Each slot is either occupied or -//! vacant. `Slab` maintains a stack of vacant slots using a linked list. To -//! find a vacant slot, the stack is popped. When a slot is released, it is -//! pushed onto the stack. -//! -//! If there are no more available slots in the stack, then `Vec::reserve(1)` is -//! called and a new slot is created. -//! -//! [`Slab::with_capacity`]: struct.Slab.html#with_capacity +//! `GenericSlab` is backed by a generic storage of slots. Each slot is either +//! occupied or vacant. `GenericSlab` maintains a stack of vacant slots using a +//! linked list. To find a vacant slot, the stack is popped. When a slot is +//! released, it is pushed onto the stack. #[cfg(not(feature = "std"))] extern crate alloc; @@ -253,13 +368,21 @@ pub type StrongSlab = GenericSlab, Vec>>>; pub type VacantEntry<'a, T> = GenericVacantEntry<'a, T, usize, Vec>>; /// A consuming iterator over the values stored in a `Slab` -pub type IntoIter = generic::IntoIter>>; +pub type IntoIter = self::generic::IntoIter>>; /// An iterator over the values stored in the `Slab` -pub type Iter<'a, T> = generic::Iter<'a, T, usize>; +pub type Iter<'a, T> = self::generic::Iter<'a, T, usize>; /// A mutable iterator over the values stored in the `Slab` -pub type IterMut<'a, T> = generic::IterMut<'a, T, usize>; +pub type IterMut<'a, T> = self::generic::IterMut<'a, T, usize>; + +/// A iterator over a specific range of values stored in the `Slab` +#[cfg(feature = "range")] +pub type RangeIter<'a, T> = self::generic::RangeIter<'a, T, usize, Vec>>; + +/// A mutable iterator over a specific range of values stored in the `Slab` +#[cfg(feature = "range")] +pub type RangeIterMut<'a, T> = self::generic::RangeIterMut<'a, T, usize, Vec>>; #[cfg(feature = "range")] pub(crate) const INVALID_INDEX: usize = core::usize::MAX; diff --git a/src/range_iter.rs b/src/range_iter.rs index fbee085..b8afad9 100644 --- a/src/range_iter.rs +++ b/src/range_iter.rs @@ -202,7 +202,7 @@ where } } -impl<'a, T, TKey, TEntries> Debug for EntriesRef<'a, T, TKey, TEntries> +impl Debug for EntriesRef<'_, T, TKey, TEntries> where TKey: Key, { @@ -275,7 +275,7 @@ where } } -impl<'a, T, TKey, TEntries> Debug for EntriesMutRef<'a, T, TKey, TEntries> +impl Debug for EntriesMutRef<'_, T, TKey, TEntries> where TKey: Key, { diff --git a/src/serde.rs b/src/serde.rs index 6177c0f..afa0511 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -42,7 +42,7 @@ where let mut builder = Builder::new(); while let Some((key, value)) = map.next_entry()? { - builder.pair(key, value) + builder.pair(key, value); } Ok(builder.build()) diff --git a/tests/range.rs b/tests/range.rs index c6b96b6..e01f5e6 100644 --- a/tests/range.rs +++ b/tests/range.rs @@ -1,5 +1,5 @@ +#![allow(missing_docs)] #![cfg(feature = "range")] -#![warn(rust_2018_idioms)] use std::ops::Bound; diff --git a/tests/serde.rs b/tests/serde.rs index 3f6869e..b54c187 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,5 +1,5 @@ +#![allow(missing_docs)] #![cfg(feature = "serde")] -#![warn(rust_2018_idioms)] use generic_slab::Slab; use serde::{Deserialize, Serialize}; diff --git a/tests/slab.rs b/tests/slab.rs index 717d7c2..dc1d0d7 100644 --- a/tests/slab.rs +++ b/tests/slab.rs @@ -1,4 +1,4 @@ -#![warn(rust_2018_idioms)] +#![allow(missing_docs)] use generic_slab::*;