0.10.0: move Clone functionality into a feature.
No more separate Git branch for it; Cargo features fit the bill well.
This commit is contained in:
parent
e84d5846bf
commit
c6480a9172
|
@ -3,6 +3,8 @@ env:
|
|||
global:
|
||||
- secure: nR+DJRUQ9v03nNZMpMu1tGKLKBAqdQsTIAr8ffdl+DUEh3b2jvQ+vLLNFLPjsloqhoOXo7cWO7qVpiE4ZOq2lNDURQjdiZGFjh/Y5+xKy2BqFdV7qQ1JoBzsMyx28tQTYz0mtBsACiCYKKb+ddNX5hpwrsjp8cS7htZktA5kbiU=
|
||||
script:
|
||||
- cargo build --verbose --features clone
|
||||
- cargo test --verbose --features clone
|
||||
- cargo build --verbose
|
||||
- cargo test --verbose
|
||||
- cargo doc --verbose
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "anymap"
|
||||
version = "0.9.13"
|
||||
version = "0.10.0"
|
||||
authors = ["Chris Morgan <me@chrismorgan.info>"]
|
||||
description = "A safe and convenient store for one value of each type"
|
||||
#documentation = "http://www.rust-ci.org/chris-morgan/anymap/doc/anymap/index.html"
|
||||
|
@ -9,3 +9,6 @@ repository = "https://github.com/chris-morgan/anymap"
|
|||
readme = "README.md"
|
||||
keywords = ["container", "data-structure", "map"]
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[features]
|
||||
clone = []
|
||||
|
|
13
README.md
13
README.md
|
@ -9,23 +9,16 @@ As another example of such an interface, JavaScript objects are exactly the same
|
|||
|
||||
Fortunately, we can do better than these things in Rust. Our type system is quite equal to easy, robust expression of such problems.
|
||||
|
||||
The ``AnyMap`` type is a friendly wrapper around a ``HashMap<TypeId, Box<Any + 'static>>``, exposing a nice, easy typed interface, perfectly safe and absolutely robust.
|
||||
The ``AnyMap`` type is a friendly wrapper around a ``HashMap<TypeId, Box<Any>>``, exposing a nice, easy typed interface, perfectly safe and absolutely robust.
|
||||
|
||||
What this means is that in an ``AnyMap`` you may store zero or one values for every type.
|
||||
|
||||
Instructions
|
||||
------------
|
||||
|
||||
Cargo all the way.
|
||||
Cargo all the way: it is `anymap` on crates.io.
|
||||
|
||||
The documentation, with examples, [is also available online](http://www.rust-ci.org/chris-morgan/anymap/doc/anymap/struct.AnyMap.html).
|
||||
|
||||
Future work
|
||||
-----------
|
||||
|
||||
I think that the only thing left for this is filling out additional methods from ``HashMap`` as appropriate.
|
||||
|
||||
It’s a very simple thing. (The initial implementation time was under ten minutes.)
|
||||
There is an optional `clone` feature on the `anymap` crate; if enabled, your `AnyMap` will require contained types to implement `Clone` and will itself satisfy `Clone`.
|
||||
|
||||
Author
|
||||
------
|
||||
|
|
145
src/lib.rs
145
src/lib.rs
|
@ -7,10 +7,10 @@
|
|||
#[cfg(test)]
|
||||
extern crate test;
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::any::TypeId;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use raw::RawAnyMap;
|
||||
use raw::{RawAnyMap, Any};
|
||||
use unchecked_any::UncheckedAnyExt;
|
||||
|
||||
macro_rules! impl_common_methods {
|
||||
|
@ -85,6 +85,8 @@ macro_rules! impl_common_methods {
|
|||
|
||||
mod unchecked_any;
|
||||
pub mod raw;
|
||||
#[cfg(feature = "clone")]
|
||||
mod with_clone;
|
||||
|
||||
/// A collection containing zero or one values for any given type and allowing convenient,
|
||||
/// type-safe access to those values.
|
||||
|
@ -98,7 +100,7 @@ pub mod raw;
|
|||
/// data.remove::<i32>();
|
||||
/// assert_eq!(data.get::<i32>(), None);
|
||||
///
|
||||
/// #[derive(PartialEq, Debug)]
|
||||
/// #[derive(Clone, PartialEq, Debug)]
|
||||
/// struct Foo {
|
||||
/// str: String,
|
||||
/// }
|
||||
|
@ -112,6 +114,7 @@ pub mod raw;
|
|||
///
|
||||
/// Values containing non-static references are not permitted.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "clone", derive(Clone))]
|
||||
pub struct AnyMap {
|
||||
raw: RawAnyMap,
|
||||
}
|
||||
|
@ -300,67 +303,103 @@ fn bench_get_present(b: &mut ::test::Bencher) {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entry() {
|
||||
#[derive(Debug, PartialEq)] struct A(i32);
|
||||
#[derive(Debug, PartialEq)] struct B(i32);
|
||||
#[derive(Debug, PartialEq)] struct C(i32);
|
||||
#[derive(Debug, PartialEq)] struct D(i32);
|
||||
#[derive(Debug, PartialEq)] struct E(i32);
|
||||
#[derive(Debug, PartialEq)] struct F(i32);
|
||||
#[derive(Debug, PartialEq)] struct J(i32);
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {AnyMap, Entry};
|
||||
|
||||
let mut map: AnyMap = AnyMap::new();
|
||||
assert_eq!(map.insert(A(10)), None);
|
||||
assert_eq!(map.insert(B(20)), None);
|
||||
assert_eq!(map.insert(C(30)), None);
|
||||
assert_eq!(map.insert(D(40)), None);
|
||||
assert_eq!(map.insert(E(50)), None);
|
||||
assert_eq!(map.insert(F(60)), None);
|
||||
#[derive(Clone, Debug, PartialEq)] struct A(i32);
|
||||
#[derive(Clone, Debug, PartialEq)] struct B(i32);
|
||||
#[derive(Clone, Debug, PartialEq)] struct C(i32);
|
||||
#[derive(Clone, Debug, PartialEq)] struct D(i32);
|
||||
#[derive(Clone, Debug, PartialEq)] struct E(i32);
|
||||
#[derive(Clone, Debug, PartialEq)] struct F(i32);
|
||||
#[derive(Clone, Debug, PartialEq)] struct J(i32);
|
||||
|
||||
// Existing key (insert)
|
||||
match map.entry::<A>() {
|
||||
Entry::Vacant(_) => unreachable!(),
|
||||
Entry::Occupied(mut view) => {
|
||||
assert_eq!(view.get(), &A(10));
|
||||
assert_eq!(view.insert(A(100)), A(10));
|
||||
#[test]
|
||||
fn test_entry() {
|
||||
let mut map: AnyMap = AnyMap::new();
|
||||
assert_eq!(map.insert(A(10)), None);
|
||||
assert_eq!(map.insert(B(20)), None);
|
||||
assert_eq!(map.insert(C(30)), None);
|
||||
assert_eq!(map.insert(D(40)), None);
|
||||
assert_eq!(map.insert(E(50)), None);
|
||||
assert_eq!(map.insert(F(60)), None);
|
||||
|
||||
// Existing key (insert)
|
||||
match map.entry::<A>() {
|
||||
Entry::Vacant(_) => unreachable!(),
|
||||
Entry::Occupied(mut view) => {
|
||||
assert_eq!(view.get(), &A(10));
|
||||
assert_eq!(view.insert(A(100)), A(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(map.get::<A>().unwrap(), &A(100));
|
||||
assert_eq!(map.len(), 6);
|
||||
assert_eq!(map.get::<A>().unwrap(), &A(100));
|
||||
assert_eq!(map.len(), 6);
|
||||
|
||||
|
||||
// Existing key (update)
|
||||
match map.entry::<B>() {
|
||||
Entry::Vacant(_) => unreachable!(),
|
||||
Entry::Occupied(mut view) => {
|
||||
let v = view.get_mut();
|
||||
let new_v = B(v.0 * 10);
|
||||
*v = new_v;
|
||||
// Existing key (update)
|
||||
match map.entry::<B>() {
|
||||
Entry::Vacant(_) => unreachable!(),
|
||||
Entry::Occupied(mut view) => {
|
||||
let v = view.get_mut();
|
||||
let new_v = B(v.0 * 10);
|
||||
*v = new_v;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(map.get().unwrap(), &B(200));
|
||||
assert_eq!(map.len(), 6);
|
||||
assert_eq!(map.get().unwrap(), &B(200));
|
||||
assert_eq!(map.len(), 6);
|
||||
|
||||
|
||||
// Existing key (remove)
|
||||
match map.entry::<C>() {
|
||||
Entry::Vacant(_) => unreachable!(),
|
||||
Entry::Occupied(view) => {
|
||||
assert_eq!(view.remove(), C(30));
|
||||
// Existing key (remove)
|
||||
match map.entry::<C>() {
|
||||
Entry::Vacant(_) => unreachable!(),
|
||||
Entry::Occupied(view) => {
|
||||
assert_eq!(view.remove(), C(30));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(map.get::<C>(), None);
|
||||
assert_eq!(map.len(), 5);
|
||||
assert_eq!(map.get::<C>(), None);
|
||||
assert_eq!(map.len(), 5);
|
||||
|
||||
|
||||
// Inexistent key (insert)
|
||||
match map.entry::<J>() {
|
||||
Entry::Occupied(_) => unreachable!(),
|
||||
Entry::Vacant(view) => {
|
||||
assert_eq!(*view.insert(J(1000)), J(1000));
|
||||
// Inexistent key (insert)
|
||||
match map.entry::<J>() {
|
||||
Entry::Occupied(_) => unreachable!(),
|
||||
Entry::Vacant(view) => {
|
||||
assert_eq!(*view.insert(J(1000)), J(1000));
|
||||
}
|
||||
}
|
||||
assert_eq!(map.get::<J>().unwrap(), &J(1000));
|
||||
assert_eq!(map.len(), 6);
|
||||
|
||||
// Entry.or_insert on existing key
|
||||
map.entry::<B>().or_insert(B(71)).0 += 1;
|
||||
assert_eq!(map.get::<B>().unwrap(), &B(201));
|
||||
assert_eq!(map.len(), 6);
|
||||
|
||||
// Entry.or_insert on nonexisting key
|
||||
map.entry::<C>().or_insert(C(300)).0 += 1;
|
||||
assert_eq!(map.get::<C>().unwrap(), &C(301));
|
||||
assert_eq!(map.len(), 7);
|
||||
}
|
||||
|
||||
#[cfg(feature = "clone")]
|
||||
#[test]
|
||||
fn test_clone() {
|
||||
let mut map = AnyMap::new();
|
||||
let _ = map.insert(A(1));
|
||||
let _ = map.insert(B(2));
|
||||
let _ = map.insert(D(3));
|
||||
let _ = map.insert(E(4));
|
||||
let _ = map.insert(F(5));
|
||||
let _ = map.insert(J(6));
|
||||
let map2 = map.clone();
|
||||
assert_eq!(map2.len(), 6);
|
||||
assert_eq!(map2.get::<A>(), Some(&A(1)));
|
||||
assert_eq!(map2.get::<B>(), Some(&B(2)));
|
||||
assert_eq!(map2.get::<C>(), None);
|
||||
assert_eq!(map2.get::<D>(), Some(&D(3)));
|
||||
assert_eq!(map2.get::<E>(), Some(&E(4)));
|
||||
assert_eq!(map2.get::<F>(), Some(&F(5)));
|
||||
assert_eq!(map2.get::<J>(), Some(&J(6)));
|
||||
}
|
||||
assert_eq!(map.get::<J>().unwrap(), &J(1000));
|
||||
assert_eq!(map.len(), 6);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! All relevant details are in the `RawAnyMap` struct.
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::any::TypeId;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::hash_map::{self, HashMap};
|
||||
use std::collections::hash_state::HashState;
|
||||
|
@ -13,10 +13,16 @@ use std::mem;
|
|||
use std::ops::{Index, IndexMut};
|
||||
use std::ptr;
|
||||
|
||||
#[cfg(not(feature = "clone"))]
|
||||
pub use std::any::Any;
|
||||
#[cfg(feature = "clone")]
|
||||
pub use with_clone::Any;
|
||||
|
||||
struct TypeIdHasher {
|
||||
value: u64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "clone", derive(Clone))]
|
||||
struct TypeIdState;
|
||||
|
||||
impl HashState for TypeIdState {
|
||||
|
@ -50,6 +56,7 @@ impl Hasher for TypeIdHasher {
|
|||
/// contents of an `AnyMap`. However, because you will then be dealing with `Any` trait objects, it
|
||||
/// doesn’t tend to be so very useful. Still, if you need it, it’s here.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "clone", derive(Clone))]
|
||||
pub struct RawAnyMap {
|
||||
inner: HashMap<TypeId, Box<Any>, TypeIdState>,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::any::Any;
|
||||
use raw::Any;
|
||||
use std::mem;
|
||||
use std::raw::TraitObject;
|
||||
|
||||
|
|
37
src/with_clone.rs
Normal file
37
src/with_clone.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use std::fmt;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait CloneToAny {
|
||||
/// Clone `self` into a new `Box<Any>` object.
|
||||
fn clone_to_any(&self) -> Box<Any>;
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone> CloneToAny for T {
|
||||
fn clone_to_any(&self) -> Box<Any> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Pretty much just `std::any::Any + Clone`.
|
||||
pub trait Any: ::std::any::Any + CloneToAny { }
|
||||
|
||||
impl<T: 'static + Clone> Any for T { }
|
||||
|
||||
impl Clone for Box<Any> {
|
||||
fn clone(&self) -> Box<Any> {
|
||||
(**self).clone_to_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for &'a Any {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad("&Any")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for Box<Any> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad("Box<Any>")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue