TypedMap

Docs Crates.io

TypedMap is a typed HashMap. It allows you to define different value type depending on a Key type. It's useful if you want to store different key-value pairs in a single hashmap, for example in HTTP app that implements multiple services.

```rust use typedmap::{TypedMap, TypedMapKey};

// Define key types

[derive(Debug, PartialEq, Eq, Hash)]

struct Dog{name: String};

[derive(Debug, PartialEq, Eq, Hash)]

struct Cat{name: String};

struct Bark{volume: u32}; struct Mew{pitch: f32};

// Define value type for key types impl TypedMapKey for Dog { type Value = Bark; } impl TypedMapKey for Cat { type Value = Mew; } // Create a new empty map let mut animalsounds: TypedMap = TypedMap::new(); // Insert data animalsounds.insert(Dog { name: "Spiky".into() }, Bark { volume: 80 }); animal_sounds.insert(Cat { name: "Spot".into() }, Mew { pitch: 12000.0 });

// Get for Dog key get value of type Bark. let spikyvolume = animalsounds.get(&Dog { name: "Spiky".into() }).unwrap().volume; asserteq!(spikyvolume, 80); ```

```rust // Define key types

[derive(Debug, PartialEq, Eq, Hash)]

struct EndpointGet(String);

[derive(Debug, PartialEq, Eq, Hash)]

struct EndpointPost(usize);

// Define value type for key types impl TypedMapKey for EndpointGet { type Value = String; }

impl TypedMapKey for EndpointPost { type Value = u32; }

// Create a new empty map let mut data: TypedMap = TypedMap::new();

// Insert data data.insert(EndpointGet("test".toowned()), "data".toowned()); data.insert(EndpointPost(32), 32); ```

You can use special Marker type to create more "type" key-value bindings.

```rust struct Configs; struct Services;

use typedmap::{TypedMap, TypedMapKey};

[derive(Debug, PartialEq, Eq, Hash)]

struct EndpointGet(String);

[derive(Debug, PartialEq, Eq, Hash)]

struct EndpointPost(usize);

struct EndpointGetConfig(String); struct EndpointPostConfig(usize);

struct EndpointGetService {}

impl TypedMapKey for EndpointGet { type Value = EndpointGetConfig; }

impl TypedMapKey for EndpointPost { type Value = EndpointPostConfig; }

impl TypedMapKey for EndpointGet { type Value = EndpointGetService; }

impl TypedMapKey for EndpointPost { type Value = EndpointPostService; }

let mut configs: TypedMap = TypedMap::new(); let mut services: TypedMap = TypedMap::new();

configs.insert(EndpointGet("test".toowned()), EndpointGetConfig("config".toowned())); configs.insert(EndpointPost(10), EndpointPostConfig(20));

services.insert(EndpointGet("test".to_owned()), EndpointGetService {}); services.insert(EndpointPost(10), EndpointPostService {}); ```

If dashmap feature is enabled, one can use TypedDashMap that can be used concurrently, as it's using Dashmap under the hood.

```rust use std::sync::Arc; use typedmap::TypedDashMap; use typedmap::TypedMapKey;

struct Configs; struct Services;

[derive(Hash, PartialEq, Eq)]

struct ServiceA(usize);

// Implement key-value mapping for Configs marker impl TypedMapKey for ServiceA { type Value = usize; }

// Implement key-value mapping for Services marker impl TypedMapKey for ServiceA { type Value = &'static str; }

[derive(Hash, PartialEq, Eq)]

struct ServiceB(&'static str);

// Implement key-value mapping for Configs marker impl TypedMapKey for ServiceB { type Value = Vec<&'static str>; }

// Implement key-value mapping for Services marker impl TypedMapKey for ServiceB { type Value = usize; }

// Implement key-value mapping for default (i.e. ()) marker impl TypedMapKey for ServiceB { type Value = String; }

let configs: Arc> = Arc::new(TypedDashMap::new()); let services: Arc> = Arc::new(TypedDashMap::new()); let default: Arc = Arc::new(TypedDashMap::new());

let configs1 = Arc::clone(&configs); let services1 = Arc::clone(&services); let t1 = std::thread::spawn(move ||{ configs1.insert(ServiceA(0), 1); services1.insert(ServiceA(0), "one"); });

// Line below would not compile, because TypeMapKey

// Line below would not compile, because SerivceA key defines // type value as usize for Configs marker (not &'static str) // configs.insert(ServiceA(0), "one");

let configs2 = Arc::clone(&configs); let services2 = Arc::clone(&services); let default2 = Arc::clone(&default); let t2 = std::thread::spawn(move || { configs2.insert(ServiceB("zero"), vec!["one"]); services2.insert(ServiceB("zero"), 32); default2.insert(ServiceB("zero"), "one".to_owned()); });

t1.join().unwrap(); t2.join().unwrap(); ```

By default TypedMap accepts keys and values that implement std::any::Any trait (and of course TypedMapKey trait), while TypedDashMap requires that keys and values meet std::any::Any + Send + Sync bounds. However, both TypedMap and TypedDashMap can be parametrized with two type parameters: key bounds (KB) and value bounds (VB). This mechanism allows to restrict what kind of keys and values can be stored in the hashmap. This crate provides four implementations of bounds: * bounds::AnyBounds - represents Any bounds (used by default in TypedMap), * bounds::SyncAnyBounds - represents Any + Sync + Send bounds (used by default in TypedDashMap), * clone::CloneBounds (if clone feature is enabled) - represents Clone + Any bounds - allows to restrict keys/values to clonable types, * clone::SyncCloneBounds (if clone feature is enabled) - represents Clone + Any + Send + Sync bounds.

These bounds can be specified in the type signature, e.g. rust use typedmap::{TypedMap, TypedMapKey}; use typedmap::clone::{CloneBounds}; let mut map: TypedMap::<(), CloneBounds, CloneBounds, _> = TypedMap::new_with_bounds();

It is possible to define own bounds using Bounds and HasBounds traits to add custom restrictions to values. For example, you may want to enforce that each value implements a custom Component trait. This can be done with a few lines of code using implcustombounds and impldyntrait_wrapper macros.

```rust // Your custom marker (could use also () as well) use typedmap::{implcustombounds, impldyntrait_wrapper, SyncAnyBounds, TypedDashMap, TypedMapKey}; struct Components;

// Trait that each value should implement trait Component { fn is_ready(&self) -> bool; }

// Bounds struct ComponentSyncBounds;

// This defines DynComponent that will be Any + Component, used internally to keep values impldyntraitwrapper!(DynComponent, Component); // This defines new "bounds" struct, that requires to impl Component and uses // dyn DynComponent + Send + Sync as a value container implcustom_bounds!(ComponentSyncBounds, DynComponent, Component, +Send+Sync);

[derive(Debug, Clone, Eq, PartialEq, Hash)]

struct RepositoryKey(usize); struct Repository(String);

impl TypedMapKey for RepositoryKey { type Value = Repository; }

impl Component for Repository { fn is_ready(&self) -> bool { true } }

// Create a new TypedDashMap that uses Components marker, keys may be Any, but value must impl Component let state: TypedDashMap = TypedDashMap::newwithbounds(); state.insert(RepositoryKey(3), Repository("three".into()));

let iter = state.iter().filter(|v| { // We can obtain reference to DynContainer let dyncontainer = v.valuecontainerref(); // and from DynContainer reference to &dyn Container using asobject function let component = dyncontainer.asobject(); component.isready() }); asserteq!(iter.count(), 1); ```

Related:

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.