diff --git a/README.md b/README.md index 5013d75..56a818c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,48 @@ This crate provides a simple token-based store for arbitrary types. -TODO: expand readme. +## What is it for? + +This crate was actually part of wayland-rs initially and extracted. The +reason for its existence is the way these crates are designed, there is +strong separation of data vs logic. + +This `token_store` works well in such configurations: you have a set of +well-separated modules that need to share data but don't need to access +it conccurently. And also, the modules are not necessarily aware of each +other (and so you can not really use a big fixed struct for storing all +the shared data. + +Using `token_store`, at initialization each module will store the value it +needs in the store, and keep the tokens internally. It can optionnaly provide +to the outside world tokens to access values that are to be shared. + +Then, when each module needs to do its work, it just needs a `&mut Store` +and can retrieve with its tokens the data it needs to work on, independently +of what other modules may have stored in. + +## How do I use it? + +```rust +use token_store::Store; + +// create a store +let mut store = Store::new(); + +// insert some things in it, you are given tokens +let token1 = store.insert(42); + +// you can store any type as log as it is `Any + 'static` +let token2 = store.insert(String::from("I like trains")); + +// the tokens keep the information of the store type, +// as such you don't need any annotation to retrieve a value: +store.get_mut(&token2).push_str(", and cars too!"); +``` + +The retrieved tokens can be cloned and shared as you like between various +parts of your code. + ## Documentation diff --git a/src/lib.rs b/src/lib.rs index 0f87432..304f537 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,43 @@ +//! # Token Store +//! +//! This crate provides a small abstraction of a type allowing +//! you to stroe values of arbitrary types and retrieving them +//! using tokens values that are cheap to move around and clone. +//! +//! The typical use-case is a data store shared by large portions +//! of an application requiring sequential access to parts of this +//! store, but while not caring nor being able to know what else +//! is stored in it. +//! +//! ## How to use it +//! +//! ``` +//! # extern crate token_store; +//! use token_store::Store; +//! +//! # fn main(){ +//! // create a store +//! let mut store = Store::new(); +//! +//! // insert some things in it, you are given tokens +//! let token1 = store.insert(42); +//! // you can store any type as log as it is `Any + 'static` +//! let token2 = store.insert(String::from("I like trains")); +//! // the tokens keep the information of the store type, +//! // as such you don't need any annotation to retrieve a value: +//! store.get_mut(&token2).push_str(", and cars too!"); +//! # } +//! ``` +//! +//! The retrieved tokens can be cloned and shared as you like between various +//! parts of your code. +//! +//! Note however that, as it is possible to store `!Send` types in the `token_store`, +//! neither the store nor its tokens can be shared accross threads. +#![warn(missing_docs)] + use std::any::Any; +use std::borrow::Cow; use std::cell::Cell; use std::marker::PhantomData; use std::rc::Rc; @@ -102,6 +141,139 @@ impl Store { live.set(false); *boxed.downcast().unwrap() } + + /// Create a sub-scope with access to a value + /// + /// In the closure you provide, the value represented by `token` + /// will be available as an argument, as well as a `StoreProxy`, + /// which allows you to manipulate the other values of the store + /// while this one is mutably borrowed. + /// + /// Attempting to access again the same value from its token from + /// within this closure is forbidden, and attempting to do so will + /// result in a panic. + /// + /// The `StoreProxy` provides the same access methods as the `Store`, + /// including `with_value`, allowing you to create nested sub-scopes + /// accessing multiple store values at the same time. + pub fn with_value(&mut self, token: &Token, f: F) -> T + where + F: FnOnce(&mut StoreProxy, &mut V) -> T, + { + self.as_proxy().with_value(token, f) + } + + /// See this `Store` as a `StoreProxy` with no ongoing borrow + /// + /// This can be usefull for code requiering access to a store, + /// but wanting to be generic over being called from a value + /// scope or not. + /// + /// You can also use the `From` and `Into` traits to perform + /// this conversion. + pub fn as_proxy<'a>(&'a mut self) -> StoreProxy<'a> { + StoreProxy { + store: self, + borrowed: Cow::Owned(Vec::new()), + } + } +} + +impl<'a> ::std::convert::From<&'a mut Store> for StoreProxy<'a> { + fn from(store: &'a mut Store) -> StoreProxy<'a> { + store.as_proxy() + } +} + +impl<'a, 'b> ::std::convert::From<&'a mut StoreProxy<'b>> for StoreProxy<'a> where 'b: 'a { + fn from(proxy: &'a mut StoreProxy<'b>) -> StoreProxy<'a> { + StoreProxy { + store: proxy.store, + borrowed: proxy.borrowed.clone() + } + } +} + +/// A Proxy representing a `Store` with ongoing borrows +/// +/// This struct represents a handle to a store from which +/// some values are already mutably borrowed, and as such +/// cannot be touched. +/// +/// See `Store::with_value` for detailed explanation of its +/// use. +pub struct StoreProxy<'store> { + store: &'store mut Store, + borrowed: Cow<'store, [usize]>, +} + +impl<'store> StoreProxy<'store> { + /// Insert a new value in the proxified store + /// + /// Returns a clonable token that you can later use to access this + /// value. + pub fn insert(&mut self, value: V) -> Token { + self.store.insert(value) + } + + /// Access value previously inserted in the proxified store + /// + /// Panics if the provided token corresponds to a value that was removed, or + /// if this value is already borrowed. + pub fn get(&self, token: &Token) -> &V { + if self.borrowed.contains(&token.id) { + panic!("Attempted to borrow twice the same value from the Store!"); + } + self.store.get(token) + } + + /// Mutably access value previously inserted in the proxified store + /// + /// Panics if the provided token corresponds to a value that was removed, or + /// if this value is already borrowed. + pub fn get_mut(&mut self, token: &Token) -> &mut V { + if self.borrowed.contains(&token.id) { + panic!("Attempted to borrow twice the same value from the Store!"); + } + self.store.get_mut(token) + } + + /// Remove a value previously inserted in the proxified store + /// + /// Panics if the provided token corresponds to a value that was already + /// removed, or if this value is already borrowed. + pub fn remove(&mut self, token: Token) -> V { + if self.borrowed.contains(&token.id) { + panic!("Attempted to remove a value from the Store while it was borrowed!"); + } + self.store.remove(token) + } + + /// Create a sub-scope with access to a value + /// + /// Panics if the provided token corresponds to a value that was removed, or + /// if this value is already borrowed. + /// + /// See `Store::with_value` for full documentation. + pub fn with_value(&mut self, token: &Token, f: F) -> T + where + F: FnOnce(&mut StoreProxy, &mut V) -> T, + { + if self.borrowed.contains(&token.id) { + panic!("Attempted to borrow twice the same value from the Store!"); + } + let value_ptr = { self.store.get_mut(token) as *mut V }; + let value = unsafe { &mut *value_ptr }; + let mut deeper_proxy = StoreProxy { + store: &mut *self.store, + borrowed: { + let mut my_borrowed = self.borrowed.clone().into_owned(); + my_borrowed.push(token.id); + Cow::Owned(my_borrowed) + }, + }; + f(&mut deeper_proxy, value) + } } #[cfg(test)] @@ -167,4 +339,80 @@ mod tests { assert_eq!(store.values.len(), 1); assert_eq!(*store.get(&token), "I like trains"); } + + #[test] + fn with_value_manipulate() { + let mut store = Store::new(); + let token1 = store.insert("I like trains".to_owned()); + let token2 = store.insert(42); + let len = store.with_value(&token1, |proxy, value1| { + *proxy.get_mut(&token2) += 10; + let token3 = proxy.with_value(&token2, |proxy, value2| { + *value2 *= 2; + proxy.insert(*value2 as f32 + 0.5) + }); + let number = proxy.remove(token2); + value1.push_str(&format!(": {} = {}", number, proxy.get(&token3))); + value1.len() + }); + assert_eq!(len, 26); + assert_eq!(store.get(&token1), "I like trains: 104 = 104.5"); + } + + #[test] + #[should_panic] + fn no_double_with_value() { + let mut store = Store::new(); + let token = store.insert(42); + store.with_value(&token, |proxy, _| { + proxy.with_value(&token, |_, _| {}); + }); + } + + #[test] + #[should_panic] + fn no_alias_get_and_with_value() { + let mut store = Store::new(); + let token = store.insert(42); + store.with_value(&token, |proxy, _| { + let _v = proxy.get(&token); + }); + } + + #[test] + #[should_panic] + fn no_alias_get_mut_and_with_value() { + let mut store = Store::new(); + let token = store.insert(42); + store.with_value(&token, |proxy, _| { + let _v = proxy.get_mut(&token); + }); + } + + #[test] + #[should_panic] + fn no_alias_remove_and_with_value() { + let mut store = Store::new(); + let token = store.insert(42); + store.with_value(&token, |proxy, _| { + let _v = proxy.remove(token.clone()); + }); + } + + #[test] + fn generic_into_store_proxy() { + fn insert_42<'a, S: Into>>(s: S) -> Token { + let mut proxy = s.into(); + proxy.insert(42) + } + + let mut store = Store::new(); + let token1 = insert_42(&mut store); + let token2 = store.with_value(&token1, |proxy, _| { + insert_42(proxy) + }); + assert_eq!(*store.get(&token1), 42); + assert_eq!(*store.get(&token2), 42); + } + }