From 6ba2f4d0fbfe70bef997388c88d6f61f2a4ebc0f Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 10 Nov 2017 03:22:23 +0000 Subject: [PATCH] Add an experimental ManagedMap container. --- Cargo.toml | 2 + README.md | 31 ++++++-- src/lib.rs | 5 ++ src/map.rs | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/object.rs | 1 - src/slice.rs | 1 - 6 files changed, 227 insertions(+), 10 deletions(-) create mode 100644 src/map.rs diff --git a/Cargo.toml b/Cargo.toml index 8883d2e..70e0dbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,5 @@ license = "0BSD" std = [] alloc = [] default = ["std"] +# Unstable features +map = [] diff --git a/README.md b/README.md index 51c23c7..8c00f7b 100644 --- a/README.md +++ b/README.md @@ -68,18 +68,25 @@ managed = { version = "...", default-features = false, features = ["..."] } ### Feature `std` -The `std` feature enables use of `Box` and `Vec` through a dependency on the `std` crate. +The `std` feature enables use of `Box`, `Vec`, and `BTreeMap` through a dependency +on the `std` crate. ### Feature `alloc` -The `alloc` feature enables use of `Box` and `Vec` through a dependency on the `alloc` crate. -This only works on nightly rustc. +The `alloc` feature enables use of `Box`, `Vec`, and `BTreeMap` through a dependency +on the `alloc` crate. This only works on nightly rustc. + +### Feature `map` + +The `map` feature, disabled by default, enables the `ManagedMap` enum. +Its interface is not stable yet and is subject to change. +It also requires the use of a nightly compiler. Usage ----- _managed_ is an interoperability crate: it does not include complex functionality but rather -defines an interface that may be used by many downstream crates. It includes two enums: +defines an interface that may be used by many downstream crates. It includes three enums: ```rust pub enum Managed<'a, T: 'a + ?Sized> { @@ -93,13 +100,21 @@ pub enum ManagedSlice<'a, T: 'a> { #[cfg(/* Vec available */)] Owned(Vec) } + +pub enum ManagedMap<'a, K: Hash + 'a, V: 'a> { + /// Borrowed variant. + Borrowed(&'a mut [(K, V)]), + /// Owned variant, only available with the `std` or `alloc` feature enabled. + #[cfg(any(feature = "std", feature = "alloc"))] + Owned(BTreeMap) +} ``` -The enums have the `From` implementations from the corresponding types, and `Deref`/`DerefMut` -implementations to the type `T`, as well as other helper methods; see the [full documentation][doc] -for details. +The `Managed` and `ManagedSlice` enums have the `From` implementations from the corresponding +types, and `Deref`/`DerefMut` implementations to the type `T`, as well as other helper methods, +and `ManagedMap` is implemented using either a B-tree map or a sorted slice of key-value pairs. -Of course, the enums can be always matched explicitly as well. +See the [full documentation][doc] for details. [doc]: https://docs.rs/managed/ diff --git a/src/lib.rs b/src/lib.rs index 272bcce..dc5d086 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] #![cfg_attr(all(feature = "alloc", not(feature = "std")), feature(alloc))] +#![cfg_attr(feature = "map", feature(slice_rotate))] //! A library that provides a way to logically own objects, whether or not //! heap allocation is available. @@ -11,6 +12,10 @@ extern crate alloc; mod object; mod slice; +#[cfg(feature = "map")] +mod map; pub use object::Managed; pub use slice::ManagedSlice; +#[cfg(feature = "map")] +pub use map::ManagedMap; diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..728a95c --- /dev/null +++ b/src/map.rs @@ -0,0 +1,197 @@ +use core::mem; +use core::fmt; +use core::borrow::Borrow; + +#[cfg(feature = "std")] +use std::collections::BTreeMap; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::btree_map::BTreeMap; + +/// A managed map. +/// +/// This enum can be used to represent exclusive access to maps. +/// In Rust, exclusive access to an object is obtained by either owning the object, +/// or owning a mutable pointer to the object; hence, "managed". +/// +/// The purpose of this enum is providing good ergonomics with `std` present while making +/// it possible to avoid having a heap at all (which of course means that `std` is not present). +/// To achieve this, the variants other than `Borrow` are only available when the corresponding +/// feature is opted in. +/// +/// Unlike [Managed](enum.Managed.html) and [ManagedSlice](enum.ManagedSlice.html), +/// the managed map is implemented using a B-tree map when allocation is available, +/// and a sorted slice of key-value pairs when it is not. Thus, algorithmic complexity +/// of operations on it depends on the kind of map. +/// +/// A function that requires a managed object should be generic over an `Into>` +/// argument; then, it will be possible to pass either a `Vec`, or a `&'a mut [T]` +/// without any conversion at the call site. +/// +/// See also [Managed](enum.Managed.html). +pub enum ManagedMap<'a, K: 'a, V: 'a> { + /// Borrowed variant. + Borrowed(&'a mut [Option<(K, V)>]), + /// Owned variant, only available with the `std` or `alloc` feature enabled. + #[cfg(any(feature = "std", feature = "alloc"))] + Owned(BTreeMap) +} + +impl<'a, K: 'a, V: 'a> fmt::Debug for ManagedMap<'a, K, V> + where K: fmt::Debug, V: fmt::Debug { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &ManagedMap::Borrowed(ref x) => write!(f, "Borrowed({:?})", x), + #[cfg(any(feature = "std", feature = "alloc"))] + &ManagedMap::Owned(ref x) => write!(f, "Owned({:?})", x) + } + } +} + +impl<'a, K: 'a, V: 'a> From<&'a mut [Option<(K, V)>]> for ManagedMap<'a, K, V> { + fn from(value: &'a mut [Option<(K, V)>]) -> Self { + ManagedMap::Borrowed(value) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl<'a, K: 'a, V: 'a> From> for ManagedMap<'a, K, V> { + fn from(value: BTreeMap) -> Self { + ManagedMap::Owned(value) + } +} + +/// Like `Option`, but with `Some` values sorting first. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +enum RevOption { + Some(T), + None +} + +impl From> for RevOption { + fn from(other: Option) -> Self { + match other { + Some(x) => RevOption::Some(x), + None => RevOption::None + } + } +} + +impl Into> for RevOption { + fn into(self) -> Option { + match self { + RevOption::Some(x) => Some(x), + RevOption::None => None + } + } +} + +fn binary_search_by_key(slice: &[Option<(K, V)>], key: &Q) -> Result + where K: Ord + Borrow, Q: Ord + ?Sized +{ + slice.binary_search_by_key(&RevOption::Some(key), |entry| { + entry.as_ref().map(|&(ref key, _)| key.borrow()).into() + }) +} + +fn pair_by_key<'a, K, Q, V>(slice: &'a [Option<(K, V)>], key: &Q) -> + Result<&'a (K, V), usize> + where K: Ord + Borrow, Q: Ord + ?Sized +{ + binary_search_by_key(slice, key).map(move |idx| slice[idx].as_ref().unwrap()) +} + +fn pair_mut_by_key<'a, K, Q, V>(slice: &'a mut [Option<(K, V)>], key: &Q) -> + Result<&'a mut (K, V), usize> + where K: Ord + Borrow, Q: Ord + ?Sized +{ + binary_search_by_key(slice, key).map(move |idx| slice[idx].as_mut().unwrap()) +} + +impl<'a, K: Ord + 'a, V: 'a> ManagedMap<'a, K, V> { + pub fn clear(&mut self) { + match self { + &mut ManagedMap::Borrowed(ref mut pairs) => { + for item in pairs.iter_mut() { + *item = None + } + }, + #[cfg(any(feature = "std", feature = "alloc"))] + &mut ManagedMap::Owned(ref mut map) => map.clear() + } + } + + pub fn get(&self, key: &Q) -> Option<&V> + where K: Borrow, Q: Ord + ?Sized + { + match self { + &ManagedMap::Borrowed(ref pairs) => { + match pair_by_key(pairs, key.borrow()) { + Ok(&(_, ref value)) => Some(value), + Err(_) => None + } + }, + #[cfg(any(feature = "std", feature = "alloc"))] + &ManagedMap::Owned(ref map) => map.get(key) + } + } + + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where K: Borrow, Q: Ord + ?Sized + { + match self { + &mut ManagedMap::Borrowed(ref mut pairs) => { + match pair_mut_by_key(pairs, key.borrow()) { + Ok(&mut (_, ref mut value)) => Some(value), + Err(_) => None + } + }, + #[cfg(any(feature = "std", feature = "alloc"))] + &mut ManagedMap::Owned(ref mut map) => map.get_mut(key) + } + } + + pub fn insert(&mut self, key: K, new_value: V) -> Result, (K, V)> { + match self { + &mut ManagedMap::Borrowed(ref mut pairs) => { + match binary_search_by_key(pairs, &key) { + Err(_) if pairs[pairs.len() - 1].is_some() => + Err((key, new_value)), // full + Err(idx) => { + let rotate_by = pairs.len() - 1; + pairs[idx..].rotate(rotate_by); + pairs[idx] = Some((key, new_value)); + Ok(None) + } + Ok(idx) => { + let mut swap_pair = Some((key, new_value)); + mem::swap(&mut pairs[idx], &mut swap_pair); + let (_key, value) = swap_pair.unwrap(); + Ok(Some(value)) + } + } + }, + #[cfg(any(feature = "std", feature = "alloc"))] + &mut ManagedMap::Owned(ref mut map) => Ok(map.insert(key, new_value)) + } + } + + pub fn remove(&mut self, key: &Q) -> Option + where K: Borrow, Q: Ord + ?Sized + { + match self { + &mut ManagedMap::Borrowed(ref mut pairs) => { + match binary_search_by_key(pairs, key) { + Ok(idx) => { + let (_key, value) = pairs[idx].take().unwrap(); + pairs[idx..].rotate(1); + Some(value) + } + Err(_) => None + } + }, + #[cfg(any(feature = "std", feature = "alloc"))] + &mut ManagedMap::Owned(ref mut map) => map.remove(key) + } + } +} + diff --git a/src/object.rs b/src/object.rs index 3222236..b7fe218 100644 --- a/src/object.rs +++ b/src/object.rs @@ -44,7 +44,6 @@ impl<'a, T: 'a + ?Sized> fmt::Debug for Managed<'a, T> #[cfg(any(feature = "std", feature = "alloc"))] &Managed::Owned(ref x) => write!(f, "Owned({:?})", x) } - } } diff --git a/src/slice.rs b/src/slice.rs index 897ef1a..06bb334 100644 --- a/src/slice.rs +++ b/src/slice.rs @@ -42,7 +42,6 @@ impl<'a, T: 'a> fmt::Debug for ManagedSlice<'a, T> #[cfg(any(feature = "std", feature = "alloc"))] &ManagedSlice::Owned(ref x) => write!(f, "Owned({:?})", x) } - } }