TxMap
A concurrent transactional hash map for Rust with fine-grained locking and internal mutability.
TxMap partitions stored key-value pairs across multiple shards, each protected by it's own parking_lot::Mutex and backed by it's own hashbrown:HashMap. Read and write operations acquire locks only on the shards they need, maximizing concurrency. Transactional operations group multiple operations into atomic units, and support parameterized closures.
Features
- Concurrent access Fine-grained shard-level locking; operations lock only the shards they touch
- Transactions Atomic, composable batches of modifications
- Optional parameterized transactions Optionally define a parameter type to pass into transaction closures
- Guards/conditions Declarative preconditions that must hold before a transaction runs
- Flexible operations Modify, map, insert, remove, swap, move, retain, and more
- Builder API Chain operations to build transactions with a fluent interface
- No
unsafe100% safe Rust
License
Licensed under the MIT License.
Usage
Add txmap to your Cargo.toml:
[]
= "0.1.0"
Creating a TxMap
use *;
// Choose a shard count, a power of two between 8 to 128 inclusive
let map: = new;
Larger shard counts reduce lock contention at the cost of slightly more memory. Choose the smallest count that gives adequate concurrency for your workload.
Key type requirements
The map key type K must implement Hash and Eq. Some functions also require Clone.
The value type V has no trait bounds by default. Operations that create default values (e.g., modify_or_default) require V: Default.
Basic operations
use *;
let map = new;
// Insert
map.insert;
// Get a value with a transform closure. Direct access to values is not supported, this is to enable concurrent modification
let balance = map.get_with;
assert_eq!;
// Remove
let old = map.remove;
assert_eq!;
// Clear all entries
map.clear;
// Length / empty
assert!;
assert_eq!;
Transactions
Transactions group multiple operations into an atomic unit. They are built using a fluent builder API.
Simple transaction (no result)
use *;
let db: = new;
db.insert;
// Transfer 50 from alice to bob in one transaction
db.transaction
.modify
.modify_or_default
.into_transaction
.execute;
assert_eq!;
assert_eq!;
Transaction with guards (preconditions)
use *;
let db: = new;
db.insert;
let tx = db
.transaction
.require
.modify
.modify_or_default
.into_transaction;
match tx.execute
If any guard condition fails, the transaction is not executed - no locks are held, no mutations occur - and TxResult::RequirementNotMet is returned.
Transaction returning a value
use *;
let db: = new;
db.insert;
let new_balance = db
.transaction
.modify
.get
.into_transaction
.execute;
assert_eq!;
Transaction returning multiple values
use *;
let db: = new;
db.insert;
db.insert;
let balances = db
.transaction
.get_all
.into_transaction
.execute;
assert_eq!;
Parameterized transactions
Parameterized transactions let you pass a parameter struct to all closures. This is useful for reusable transaction logic.
use *;
let db: = new;
db.insert;
let transfer_alice_to_bob_tx = db
.transaction
.
.require
.modify
.modify_or_default
.get_all
.into_transaction;
// Execute with different parameters
let result1 = transfer_alice_to_bob_tx.execute;
assert_eq!;
let result2 = transfer_alice_to_bob_tx.execute;
assert_eq!;
Operation reference
| Builder method | Description | Additional required bounds |
|---|---|---|
insert_default |
Insert V::default() for the key. |
K: Clone V: Default |
insert_default_if_absent |
Insert V::default() for the key, only if the key is absent. |
K: Clone V: Default |
insert_with |
Insert a value generated from the key. | K: Clone |
insert_with_if_absent |
Insert a value generated from the key, only if the key is absent. | K: Clone |
modify |
Mutate an existing value in-place. Does nothing if key absent. | V: Default |
modify_peek |
Like modify while peeking at other values. |
K: Clone |
update |
Update a single entry. Return Some(v) to insert/replace, None to delete. |
K: Clone |
update_peek |
Like update while peeking at other values. |
K: Clone |
move_value |
Remove a value from one key and insert it with another key. | K: Clone |
swap_value |
Swap the values of two keys. | K: Clone |
remove |
Remove the given keys. | |
remove_where |
Remove the given keys which also satisfy a condition. | |
retain_only |
Retain only the given keys. | |
retain_where |
Retain only the given keys which also satisfy a condition. | |
clear |
Remove all entries. | |
remove_if |
Remove any entries which satisfy a condition. | |
retain |
Retain only the entries which satisfy a condition. |
Finisher methods
Up to one of these is called before into_transaction() in the builder chain to define what the transaction should return.
| Method | Description | Transaction result type |
|---|---|---|
| (none - default) | Execute with no return value. | TxResult<()> |
get_copied(key) |
Copy a single value. Requires V: Copy |
TxResult<Option<V>> |
get_cloned(key) |
Clone a single value. Requires V: Clone |
TxResult<Option<V>> |
get(key, |k, v[, params]| { ... }) |
Read a single value and apply a transformation to it. | TxResult<Option<R>> |
get_all(keys, |k, v[, params]| { ... }) |
Read multiple values and apply a transformation to them. | TxResult<Vec<Option<R>>> |
To create the final transaction call into_transaction(). This will produce a re-useable transaction that can be executed as many times as you want within the lifetime of it's TxMap.
TxResult
All transactions return TxResult<T>:
Completed(result)The transaction was executed successfully.RequirementNotMet(index, name)A guard condition failed; the transaction was aborted. Theindexindicates which guard failed, andnameis the user-supplied description.
Operation appendix
| Operation |
|---|
insert_default(key) |
insert_default_if_absent(key) |
insert_with(key, |k[, params]| { new_value } ) |
insert_with_if_absent(key, |k[, params]| { new_value } ) |
modify(key, |k, mut v[, params]|) |
modify_peek(key, peek_keys, |k, mut v, pks[, params]|) |
update(key, |k, v_opt[, params]| { new_value_opt }) |
update_peek(key, peek_keys, |k, v_opt, pks[, params]| { new_value_opt }) |
move_value(from, to) |
swap_value(a, b) |
remove(keys) |
remove_where(keys, |k, v[, params]| { remove }) |
retain_only(keys) |
retain_where(keys, |k, v[, params]| { remove }) |
clear() |
remove_if(|k, v[, params]| { remove }) |
retain(|k, v[, params]| { remove }) |