From e8b6378a8fe531f3402002294cf86af6cac02916 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 11 Oct 2017 11:43:04 +0200 Subject: [PATCH 1/4] Add the with_value API --- src/lib.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0f87432..8ceb176 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,89 @@ impl Store { live.set(false); *boxed.downcast().unwrap() } + + 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) + } + + + pub fn as_proxy<'a>(&'a mut self) -> StoreProxy<'a> { + StoreProxy { + store: self, + borrowed: Vec::new(), + } + } +} + +pub struct StoreProxy<'store> { + store: &'store mut Store, + borrowed: Vec, +} + +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. + 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) + } + + 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(); + my_borrowed.push(token.id); + my_borrowed + }, + }; + f(&mut deeper_proxy, value) + } } #[cfg(test)] @@ -167,4 +250,23 @@ 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"); + } } From 6a33b408e267884000d5126539023a2a9cfc4df1 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 11 Oct 2017 11:50:34 +0200 Subject: [PATCH 2/4] More tests on with_value API --- src/lib.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 8ceb176..6f432cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -269,4 +269,46 @@ mod tests { 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()); + }); + } + } From 9853006f62e2a2d2ac306b99dace3732787f8dcf Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 11 Oct 2017 13:40:15 +0200 Subject: [PATCH 3/4] Fill in documentation --- README.md | 43 ++++++++++++++++++++++++++++++- src/lib.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 3 deletions(-) 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 6f432cb..e61ff76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,41 @@ +//! # 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::cell::Cell; use std::marker::PhantomData; @@ -103,6 +141,20 @@ impl Store { *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, @@ -110,7 +162,11 @@ impl Store { 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. pub fn as_proxy<'a>(&'a mut self) -> StoreProxy<'a> { StoreProxy { store: self, @@ -119,6 +175,14 @@ impl Store { } } +/// 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: Vec, @@ -158,7 +222,7 @@ impl<'store> StoreProxy<'store> { /// Remove a value previously inserted in the proxified store /// /// Panics if the provided token corresponds to a value that was already - /// removed. + /// 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!"); @@ -166,6 +230,12 @@ impl<'store> StoreProxy<'store> { 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, From dc27849e0e2c3772632f93c217dfa1da656a501f Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 11 Oct 2017 13:51:46 +0200 Subject: [PATCH 4/4] impl From<&mut Store> for StoreProxy --- src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e61ff76..304f537 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,7 @@ #![warn(missing_docs)] use std::any::Any; +use std::borrow::Cow; use std::cell::Cell; use std::marker::PhantomData; use std::rc::Rc; @@ -167,10 +168,28 @@ impl Store { /// 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: Vec::new(), + 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() } } } @@ -185,7 +204,7 @@ impl Store { /// use. pub struct StoreProxy<'store> { store: &'store mut Store, - borrowed: Vec, + borrowed: Cow<'store, [usize]>, } impl<'store> StoreProxy<'store> { @@ -248,9 +267,9 @@ impl<'store> StoreProxy<'store> { let mut deeper_proxy = StoreProxy { store: &mut *self.store, borrowed: { - let mut my_borrowed = self.borrowed.clone(); + let mut my_borrowed = self.borrowed.clone().into_owned(); my_borrowed.push(token.id); - my_borrowed + Cow::Owned(my_borrowed) }, }; f(&mut deeper_proxy, value) @@ -346,8 +365,7 @@ mod tests { let mut store = Store::new(); let token = store.insert(42); store.with_value(&token, |proxy, _| { - proxy.with_value(&token, |_, _| { - }); + proxy.with_value(&token, |_, _| {}); }); } @@ -381,4 +399,20 @@ mod tests { }); } + #[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); + } + }