1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! _**Note:** This crate is still in early development and undergoing API changes. Contributions, feature requests, and
//! constructive feedback are warmly welcomed._
//!
//! # sharded   ![Build] ![Crate]
//!
//! [Build]: https://github.com/nkconnor/sharded/workflows/build/badge.svg
//! [Crate]: https://img.shields.io/crates/v/sharded
//!
//! **Sharded provides safe, fast, and obvious concurrent collections in Rust**. This crate splits the
//! underlying collection into `N shards` each with its own lock. Calling `read(key)` or `write(key)`
//! returns a guard for a single shard.
//!
//! ## Features
//!
//! * **Zero unsafe code.** This library uses `#![forbid(unsafe_code)]`. There are some limitations with the
//! raw locking API that _could cause you to write a bug_, but it should be hard to so!
//!
//! * **Zero dependencies.** By default, the library only uses `std`. If you'd like to pull in some community
//! crates such as `parking_lot`, just use the **3rd-party** feature.
//!
//! * **Tiny footprint.** The core logic is ~100 lines of code. This may build up over time as utilities
//! and ergonomics are added.
//!
//! * ~~**Extremely fast.** This implementation may be a more performant choice for your workload than some
//! of the most popular concurrent hashmaps out there.~~ **??**
//!
//! * **Flexible API.**. Bring your own lock or collection types. `sharded::Map` is just a type alias for
//! `Shard<Lock<Collection<_>>>`. There's support for Sets and Trees, too!
//!
//!
//! ### See Also
//!
//! - **[flurry](https://github.com/jonhoo/flurry)** - A port of Java's `java.util.concurrent.ConcurrentHashMap` to Rust. (Also part of a live stream series)
//! - **[dashmap](https://github.com/xacrimon/dashmap)** - Blazing fast concurrent HashMap for Rust.
//! - **[countrie](https://crates.io/crates/contrie)** - A concurrent hash-trie map & set.
//!
//!
//! ## Quick Start
//!
//! ```toml
//! [dependencies]
//!
//! # Optionally use `parking_lot`, `hashbrown`, and `ahash`
//! # by specifing the feature "3rd-party"
//! sharded = { version = "0.0.1", features = ["3rd-party"] }
//! ```
//! ### Examples
//!
//! **Use a concurrent HashMap**
//!
//! ```ignore
//! use sharded::Map;
//! let concurrent = Map::new()
//!
//! // or use an existing HashMap,
//!
//! let users = Shard::from(users);
//!
//! let guard = users.write(32);
//! guard.insert(32, user);
//! ```
//!
//! ## Acknowledgements
//!
//! Many thanks to
//!
//! - [Reddit community](https://www.reddit.com/r/rust) for a few pointers and
//! some motivation to take this project further.
//!
//! - [Jon Gjengset](https://github.com/jonhoo) for the live streams and utility crates involved
//!
//! - and countless OSS contributors that made this work possible
//!
//! ## License
//!
//! Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
//! 2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
//!
//! Unless you explicitly state otherwise, any contribution intentionally submitted
//! for inclusion in `sharded` by you, as defined in the Apache-2.0 license, shall be
//! dual licensed as above, without any additional terms or conditions.

#![forbid(unsafe_code)]
#![allow(dead_code)]
#![allow(unused_macros)]
#![allow(incomplete_features)]
#![feature(generic_associated_types)]
#![feature(in_band_lifetimes)]

#[cfg(feature = "fxhash")]
use fxhash_utils::FxHasher as DefaultHasher;

#[cfg(feature = "fxhash")]
use fxhash_utils::FxBuildHasher as DefaultRandomState;

#[cfg(feature = "ahash")]
use ahash_utils::AHasher as DefaultHasher;

#[cfg(feature = "ahash")]
use ahash_utils::RandomState as DefaultRandomState;

#[cfg(not(any(feature = "ahash", feature = "fxhash")))]
use std::collections::hash_map::DefaultHasher;

#[cfg(not(any(feature = "ahash", feature = "fxhash")))]
use std::collections::hash_map::RandomState as DefaultRandomState;

#[cfg(feature = "hashbrown")]
use hashbrown_utils::HashMap;

#[cfg(feature = "hashbrown")]
use hashbrown_utils::HashSet;

#[cfg(not(feature = "hashbrown"))]
use std::collections::HashMap;

#[cfg(not(feature = "hashbrown"))]
use std::collections::HashSet;

use std::hash::Hash;

mod lock;
pub use lock::Lock;
pub use lock::RwLock;
pub use lock::ShardLock;

mod collection;
pub use collection::Collection;
pub type RandomState = DefaultRandomState;

mod shard;

pub use shard::ExtractShardKey;
pub use shard::Shard;

/// Sharded lock-based concurrent map using the crate default lock and map implementations.
pub type Map<K, V, S = RandomState> = Shard<RwLock<HashMap<K, V, S>>>;

/// Sharded lock-based concurrent set using the crate default lock and set implementations.
pub type Set<K> = Shard<RwLock<HashSet<K>>>;

impl<K: Hash + Eq + Clone, V: Clone> Map<K, V> {
    pub fn new() -> Self {
        Shard::from(HashMap::<K, V, RandomState>::with_hasher(
            RandomState::default(),
        ))
    }

    pub fn with_capacity(capacity: usize) -> Self {
        Shard::from(HashMap::<K, V, RandomState>::with_capacity_and_hasher(
            capacity,
            RandomState::default(),
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn read_and_write() {
        let x = Map::new();

        x.write(&"key".to_string())
            .insert("key".to_string(), "value".to_string());

        assert_eq!(
            x.read(&"key".to_string()).get(&"key".to_string()).unwrap(),
            "value"
        );
    }

    #[test]
    fn hold_read_and_write() {
        let map = Map::new();
        let mut write = map.write(&"abc".to_string());
        write.insert("abc".to_string(), "asdf".to_string());

        let _read = map.read(&"asdfas".to_string());
        let _read_too = map.read(&"asdfas".to_string());
        assert!(_read.is_empty());
    }
}