lending_library/lib.rs
1/* Notice
2lib.rs: lending-library
3
4Copyright 2018 Thomas Bytheway <thomas.bytheway@cl.cam.ac.uk>
5
6This file is part of the lending-library open-source project: github.com/harkonenbade/lending-library;
7Its licensing is governed by the LICENSE file at the root of the project.
8*/
9#![warn(missing_docs)]
10
11//! A data store that lends temporary ownership of stored values.
12//!
13//! # Example
14//! ```
15//! use lending_library::*;
16//!
17//! struct Processor;
18//! struct Item(String);
19//!
20//! impl Item {
21//! fn gen(dat: &str) -> Self { Item(dat.to_string()) }
22//! }
23//!
24//! impl Processor {
25//! fn link(&self, _first: &Item, _second: &Item) {}
26//! }
27//!
28//! enum Event {
29//! Foo {
30//! id: i64,
31//! dat: &'static str,
32//! },
33//! Bar {
34//! id: i64,
35//! o_id: i64,
36//! o_dat: &'static str,
37//! }
38//! }
39//!
40//! const EVTS: &[Event] = &[Event::Foo {id:1, dat:"a_val"},
41//! Event::Foo {id:2, dat:"b_val"},
42//! Event::Bar {id:1, o_id: 2, o_dat:"B_val"},
43//! Event::Bar {id:1, o_id: 3, o_dat:"c_val"}];
44//!
45//! struct Store {
46//! id_gen: Box<Iterator<Item = i64>>,
47//! id_to_dat: LendingLibrary<i64, Item>,
48//! }
49//!
50//! impl Store {
51//! fn new() -> Self {
52//! Store {
53//! id_gen: Box::new(0..),
54//! id_to_dat: LendingLibrary::new(),
55//! }
56//! }
57//!
58//! pub fn declare(&mut self, uid: i64, dat: &str) -> Loan<i64, Item> {
59//! if !self.id_to_dat.contains_key(&uid) {
60//! self.id_to_dat.insert(uid, Item::gen(dat));
61//! }
62//! self.id_to_dat.lend(&uid).unwrap()
63//! }
64//! }
65//!
66//! fn main() {
67//! let mut store = Store::new();
68//! let pro = Processor;
69//! for evt in EVTS {
70//! match *evt {
71//! Event::Foo { id, dat } => {
72//! store.declare(id, dat);
73//! }
74//! Event::Bar { id, o_id, o_dat } => {
75//! let i = store.declare(id, "");
76//! let o = store.declare(o_id, o_dat);
77//! pro.link(&i, &o);
78//! }
79//! }
80//! }
81//! }
82//! ```
83
84pub mod iter;
85mod loan;
86#[cfg(test)]
87mod tests;
88
89pub use loan::Loan;
90
91use std::{
92 collections::{hash_map::DefaultHasher, HashMap},
93 hash::{Hash, Hasher},
94 sync::atomic::{AtomicUsize, Ordering},
95 thread,
96};
97
98enum State<K, V> {
99 Present(K, V),
100 Loaned(K),
101 AwaitingDrop(K),
102}
103
104use self::State::{AwaitingDrop, Loaned, Present};
105
106/// A key-value data store that allows you to loan temporary ownership of values.
107///
108/// # Assumptions
109/// The store does it's best to ensure that no unsafe behaviour occurs, however as a result it may
110/// trigger several panics rather than allow an unsafe condition to arise.
111///
112/// The main panic condition is that a `Loan` object derived from the `lend` method on a store may
113/// never outlive the store it originated from. If this condition happens the store will generate a
114/// panic as it goes out of scope, noting the number of outstanding `Loan` objects.
115pub struct LendingLibrary<K, V>
116where
117 K: Hash,
118{
119 store: HashMap<u64, State<K, V>>,
120 outstanding: AtomicUsize,
121}
122
123fn _hash<K: Hash>(val: &K) -> u64 {
124 let mut hasher = DefaultHasher::new();
125 (*val).hash(&mut hasher);
126 hasher.finish()
127}
128
129impl<K, V> LendingLibrary<K, V>
130where
131 K: Hash,
132{
133 /// Creates a new empty `LendingLibrary`.
134 /// # Example
135 /// ```
136 /// use lending_library::LendingLibrary;
137 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::new();
138 /// ```
139 pub fn new() -> LendingLibrary<K, V> {
140 LendingLibrary {
141 store: HashMap::new(),
142 outstanding: AtomicUsize::new(0),
143 }
144 }
145
146 /// Creates an empty `LendingLibrary` with at least the specified capacity.
147 /// The library will be able to hold at least `capacity` elements without reallocating.
148 /// # Example
149 /// ```
150 /// use lending_library::LendingLibrary;
151 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::with_capacity(100);
152 /// ```
153 pub fn with_capacity(capacity: usize) -> LendingLibrary<K, V> {
154 LendingLibrary {
155 store: HashMap::with_capacity(capacity),
156 outstanding: AtomicUsize::new(0),
157 }
158 }
159
160 /// Returns the number of elements the library can store without reallocating.
161 /// The same bounds as [`HashMap::capacity()`] apply.
162 ///
163 /// [`HashMap::capacity()`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html#method.capacity
164 /// # Example
165 /// ```
166 /// use lending_library::LendingLibrary;
167 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::with_capacity(100);
168 /// assert!(lib.capacity() >= 100);
169 /// ```
170 pub fn capacity(&self) -> usize {
171 self.store.capacity()
172 }
173
174 /// Reserves space such that the library can store at least `additional` new records without reallocating.
175 /// # Example
176 /// ```
177 /// use lending_library::LendingLibrary;
178 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::with_capacity(0);
179 /// assert_eq!(lib.capacity(), 0);
180 /// lib.reserve(10);
181 /// assert!(lib.capacity() >= 10);
182 /// ```
183 pub fn reserve(&mut self, additional: usize) {
184 self.store.reserve(additional)
185 }
186
187 /// Reduces the stores capacity to the minimum currently required.
188 /// # Example
189 /// ```
190 /// use lending_library::LendingLibrary;
191 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::with_capacity(10);
192 /// assert!(lib.capacity() >= 10);
193 /// lib.shrink_to_fit();
194 /// assert_eq!(lib.capacity(), 0);
195 /// ```
196 pub fn shrink_to_fit(&mut self) {
197 self.store.shrink_to_fit()
198 }
199
200 /// An iterator visiting all key/value pairs in arbitary order.
201 /// The item type is `(&'a K, &'a V)`
202 /// # Panics
203 /// The iterator will panic if it encounters an item that is currently loaned from the store,
204 /// so this should only be used where you are sure you have returned all loaned items.
205 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
206 self.into_iter()
207 }
208
209 /// An iterator visiting all key/value pairs in arbitary order, with mutable references to the
210 /// values. The item type is `(&'a K, &'a mut V)`
211 /// # Panics
212 /// The iterator will panic if it encounters an item that is currently loaned from the store,
213 /// so this should only be used where you are sure you have returned all loaned items.
214 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
215 self.into_iter()
216 }
217
218 /// Returns the number of items in the store.
219 /// # Example
220 /// ```
221 /// use lending_library::LendingLibrary;
222 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::new();
223 /// lib.insert(1, 1);
224 /// lib.insert(2, 1);
225 /// assert_eq!(lib.len(), 2);
226 /// ```
227 pub fn len(&self) -> usize {
228 self.store
229 .iter()
230 .map(|(_k, v)| match v {
231 Present(..) | Loaned(_) => 1,
232 AwaitingDrop(_) => 0,
233 })
234 .sum()
235 }
236
237 /// Returns true if the store is empty and false otherwise.
238 /// # Example
239 /// ```
240 /// use lending_library::LendingLibrary;
241 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::new();
242 /// assert!(lib.is_empty());
243 /// lib.insert(1, 1);
244 /// lib.insert(2, 1);
245 /// assert!(!lib.is_empty());
246 /// ```
247 pub fn is_empty(&self) -> bool {
248 self.len() == 0
249 }
250
251 /// Removes all items from the store.
252 /// # Example
253 /// ```
254 /// use lending_library::LendingLibrary;
255 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::new();
256 /// lib.insert(1, 1);
257 /// lib.insert(2, 1);
258 /// {
259 /// let v = lib.lend(&2).unwrap();
260 /// assert_eq!(*v, 1);
261 /// }
262 /// lib.clear();
263 /// assert_eq!(lib.lend(&1), None);
264 /// ```
265 pub fn clear(&mut self) {
266 let new_store = self
267 .store
268 .drain()
269 .filter(|&(_k, ref v)| match v {
270 Present(..) => false,
271 Loaned(_) | AwaitingDrop(_) => true,
272 })
273 .map(|(h, v)| match v {
274 Loaned(k) | AwaitingDrop(k) => (h, AwaitingDrop(k)),
275 Present(..) => unreachable!(),
276 })
277 .collect();
278 self.store = new_store;
279 }
280
281 /// Returns true if a record with key `key` exists in the store, and false otherwise.
282 /// # Example
283 /// ```
284 /// use lending_library::LendingLibrary;
285 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::new();
286 /// assert!(!lib.contains_key(&1));
287 /// lib.insert(1, 1);
288 /// assert!(lib.contains_key(&1));
289 /// ```
290 pub fn contains_key(&self, key: &K) -> bool {
291 let h = _hash(key);
292 match self.store.get(&h) {
293 Some(v) => match v {
294 Present(..) | Loaned(_) => true,
295 AwaitingDrop(_) => false,
296 },
297 None => false,
298 }
299 }
300
301 /// Inserts a new key/value pair into the store. If a pair with that key already exists, the
302 /// previous values will be returned as `Some(V)`, otherwise the method returns `None`.
303 /// # Panics
304 /// The method will panic if you attempt to overwrite a key/value pair that is currently loaned.
305 /// # Example
306 /// ```
307 /// use lending_library::LendingLibrary;
308 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::new();
309 /// lib.insert(1, 1);
310 /// lib.insert(2, 1);
311 /// ```
312 pub fn insert(&mut self, key: K, val: V) -> Option<V> {
313 let h = _hash(&key);
314 match self.store.insert(h, Present(key, val)) {
315 Some(v) => match v {
316 Present(_, v) => Some(v),
317 Loaned(_) => panic!("Cannot overwrite loaned value"),
318 AwaitingDrop(_) => panic!("Cannot overwrite value awaiting drop"),
319 },
320 None => None,
321 }
322 }
323
324 /// Removes a key/value pair from the store. Returning true if the key was present in the store
325 /// and false otherwise.
326 /// # Example
327 /// ```
328 /// use lending_library::LendingLibrary;
329 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::new();
330 /// assert!(!lib.remove(&1));
331 /// lib.insert(1, 1);
332 /// assert!(lib.contains_key(&1));
333 /// assert!(lib.remove(&1));
334 /// assert!(!lib.contains_key(&1));
335 /// assert!(!lib.remove(&1));
336 /// ```
337 pub fn remove(&mut self, key: &K) -> bool {
338 let h = _hash(key);
339 match self.store.remove(&h) {
340 Some(v) => match v {
341 Present(..) => true,
342 Loaned(k) => {
343 self.store.insert(h, AwaitingDrop(k));
344 true
345 }
346 AwaitingDrop(k) => {
347 self.store.insert(h, AwaitingDrop(k));
348 false
349 }
350 },
351 None => false,
352 }
353 }
354
355 /// Loans a value from the library, returning `Some(Loan<K, V>)` if the value is present, and `None` if it is not.
356 /// # Panics
357 /// Will panic if you try and loan a value that still has an outstanding loan.
358 /// # Examples
359 /// ```
360 /// use lending_library::LendingLibrary;
361 /// let mut lib: LendingLibrary<i32, i32> = LendingLibrary::with_capacity(0);
362 /// lib.insert(1, 1);
363 /// {
364 /// let mut v = lib.lend(&1).unwrap();
365 /// *v += 5;
366 /// }
367 /// ```
368 pub fn lend(&mut self, key: &K) -> Option<Loan<K, V>> {
369 let h = _hash(key);
370 let ptr: *mut Self = self;
371 match self.store.remove(&h) {
372 Some(v) => match v {
373 Present(k, v) => {
374 self.outstanding.fetch_add(1, Ordering::Relaxed);
375 self.store.insert(h, Loaned(k));
376 Some(Loan {
377 owner: ptr,
378 key: h,
379 inner: Some(v),
380 })
381 }
382 Loaned(_) => panic!("Lending already loaned value"),
383 AwaitingDrop(_) => panic!("Lending value awaiting drop"),
384 },
385 None => None,
386 }
387 }
388
389 fn checkin(&mut self, key: u64, val: V) {
390 match self.store.remove(&key) {
391 Some(v) => {
392 self.outstanding.fetch_sub(1, Ordering::Relaxed);
393 match v {
394 Present(..) => panic!("Returning replaced item"),
395 Loaned(k) => {
396 self.store.insert(key, Present(k, val));
397 }
398 AwaitingDrop(_) => {}
399 }
400 }
401 None => panic!("Returning item not from store"),
402 }
403 }
404}
405
406impl<K, V> Drop for LendingLibrary<K, V>
407where
408 K: Hash,
409{
410 fn drop(&mut self) {
411 if !thread::panicking() {
412 let count = self.outstanding.load(Ordering::SeqCst);
413 if count != 0 {
414 panic!("{} value loans outlived store.", count)
415 }
416 }
417 }
418}
419
420impl<K, V> Default for LendingLibrary<K, V>
421where
422 K: Hash,
423{
424 fn default() -> Self {
425 LendingLibrary::new()
426 }
427}