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 its own parking_lot::Mutex and backed by its 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., insert_default) require V: Default.
Transactions
Transactions group multiple operations into an atomic unit. They are built using a fluent builder API. Use .into_transaction() to produce a reusable transaction, then .execute() to run it.
Simple transaction (no result)
use *;
let db: = new;
db.insert;
// Transfer 50 from alice to bob in one transaction
db.transaction
.modify
.modify
.into_transaction
.execute;
assert_eq!;
assert_eq!;
Transaction with guards (preconditions)
Guards are checked before any mutations take place. If any guard fails, the transaction is not executed, all locks are dropped, no mutations occur, and TxResult::RequirementNotMet is returned.
use *;
let db: = new;
db.insert;
db.insert;
let tx = db
.transaction
.require
.modify
.modify
.into_transaction;
assert!;
Transaction returning a value
Use one of .get(), .get_copied(), .get_cloned(), get_all(), .get_all_copied() or .get_all_cloned() before .into_transaction() to return a value or values at the end of the transaction.
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;
let balances = db
.transaction
.insert_with_if_absent
.insert_with_if_absent
.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
.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!;
Finite state machine example
Use update to implement state transitions that return Some(new_state) to update or None to delete.
use *;
let orders: = new;
orders.insert;
// Transition order-1 from Pending to Shipped
let result = orders
.transaction
.update
.get_cloned
.into_transaction
.execute;
assert_eq!;
Swap and move operations
Atomically swap values between two keys, or move a value from one key to another.
use *;
let map: = new;
map.insert;
map.insert;
// Swap values
let result = map
.transaction
.swap_value
.get_all_copied
.into_transaction
.execute;
assert_eq!;
// Move value (from "a" to "b", leaving "a" empty)
let result = map
.transaction
.move_value
.get_all_copied
.into_transaction
.execute;
assert_eq!;
Batch operations
Conditionally remove or retain entries in bulk.
use *;
let map: = new;
map.insert;
map.insert;
map.insert;
// Remove entries with values > 1
map.transaction
.remove_if
.into_transaction
.execute;
assert_eq!; // only alice remains
// Retain only specific keys
map.insert;
map.transaction
.retain_only
.into_transaction
.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. | |
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() to define what the transaction should return.
| Method | Description | Transaction result type | Required bound |
|---|---|---|---|
| (none - default) | Execute with no return value. | TxResult<()> |
|
get_copied(key) |
Copy a single value. | TxResult<Option<V>> |
V: Copy |
get_all_copied(keys) |
Copy an array of values. | TxResult<Vec<Option<V>>> |
V: Copy |
get_cloned(key) |
Clone a single value. | TxResult<Option<V>> |
V: Clone |
get_all_cloned(keys) |
Clone an array of values. | TxResult<Vec<Option<V>>> |
V: Clone |
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 its 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[, 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 }) |