| Crates.io | smr-swap |
| lib.rs | smr-swap |
| version | 0.9.0 |
| created_at | 2025-11-12 18:21:09.806508+00 |
| updated_at | 2025-12-26 10:24:16.516189+00 |
| description | A minimal-locking, high-performance Single-Writer Multiple-Reader swap container using epoch-based memory reclamation |
| homepage | https://github.com/ShaoG-R/smr-swap |
| repository | https://github.com/ShaoG-R/smr-swap |
| max_upload_size | |
| id | 1929732 |
| size | 158,627 |
A high-performance Rust library for safely sharing mutable data between a single writer and multiple readers using version-based memory reclamation.
SmrSwap, LocalReader, ReadGuardswmr-cell) to prevent use-after-freeno_std environments (requires alloc)Add to your Cargo.toml:
[dependencies]
smr-swap = "0.8"
Use with default-features = false and enable spin feature (if you need the default spinlock-based mutex implementation in swmr-cell):
[dependencies]
smr-swap = { version = "0.8", default-features = false, features = ["spin"] }
use smr_swap::SmrSwap;
use std::thread;
fn main() {
// Create a new SMR container
let mut swap = SmrSwap::new(0);
// Get a thread-local reader
let local = swap.local();
// Writer stores a new value
swap.store(1);
// Read in another thread
let local2 = swap.local();
let handle = thread::spawn(move || {
let guard = local2.load();
assert_eq!(*guard, 1);
});
handle.join().unwrap();
}
use smr_swap::SmrSwap;
use std::thread;
fn main() {
let mut swap = SmrSwap::new(vec![1, 2, 3]);
// Create independent LocalReader for each thread
let readers: Vec<_> = (0..4).map(|_| swap.local()).collect();
let handles: Vec<_> = readers
.into_iter()
.enumerate()
.map(|(i, local)| {
thread::spawn(move || {
let guard = local.load();
println!("Thread {} sees: {:?}", i, *guard);
})
})
.collect();
// Writer stores a new value
swap.store(vec![4, 5, 6]);
for handle in handles {
handle.join().unwrap();
}
}
use smr_swap::SmrSwap;
fn main() {
let mut swap = SmrSwap::new(vec![1, 2, 3]);
let local = swap.local();
// Use map to transform value
let sum: i32 = local.map(|v| v.iter().sum());
println!("Sum: {}", sum);
// Use filter to conditionally get guard
if let Some(guard) = local.filter(|v| v.len() > 2) {
println!("Vector has more than 2 elements: {:?}", *guard);
}
// Use update_and_fetch to update and get new value
let new_guard = swap.update_and_fetch(|v| {
let mut new_vec = v.clone();
new_vec.push(4);
new_vec
});
println!("New value: {:?}", *new_guard);
}
SmrReaderIf you need to distribute the ability to create readers to multiple threads (e.g., in a dynamic thread pool), use SmrReader. Unlike LocalReader, SmrReader is Sync and Clone.
use smr_swap::SmrSwap;
use std::thread;
let mut swap = SmrSwap::new(0);
// Create a shareable SmrReader factory
let reader_factory = swap.reader();
for i in 0..3 {
// Clone the factory for each thread
let factory = reader_factory.clone();
thread::spawn(move || {
// Use factory to create a LocalReader on the thread
let local = factory.local();
// ... use local reader ...
});
}
| Type | Role | Key Methods |
|---|---|---|
SmrSwap<T> |
Main container, holds data and write capability | new(), store(), get(), load(), local(), swap() |
LocalReader<T> |
Thread-local read handle | load(), map(), filter(), is_pinned(), version() |
SmrReader<T> |
Cross-thread reader factory | local() |
ReadGuard<'a, T> |
RAII guard, protects data during read | Deref, AsRef, version() |
SmrSwap ──local()──► LocalReader ──load()──► ReadGuard
(main) (per-thread) (RAII guard)
LocalReader is a thread-local read handle:
LocalReader and reuse itLocalReader is Send but not Sync, should not be shared across threadsSmrSwap<T>Main entry point, holds data and write capability.
| Method | Description |
|---|---|
new(initial: T) |
Create a new container |
local() -> LocalReader<T> |
Create a thread-local read handle |
reader() -> SmrReader<T> |
Create a shareable reader factory |
store(new_value: T) |
Store a new value, old value will be safely reclaimed |
get() -> &T |
Get reference to current value (writer-only, no pin required) |
update(f: FnOnce(&T) -> T) |
Update value using a closure |
load() -> ReadGuard<T> |
Read current value using internal handle |
load_cloned() -> T |
Load and clone the current value (requires T: Clone) |
swap(new_value: T) -> T |
Swap value and return old value (requires T: Clone) |
update_and_fetch(f) -> ReadGuard<T> |
Apply closure to update and return guard to new value |
fetch_and_update(f) -> ReadGuard<T> |
Apply closure to update and return guard to old value |
version() -> usize |
Get current global version |
garbage_count() -> usize |
Get number of objects waiting for garbage collection |
previous() -> Option<&T> |
Get reference to previously stored value |
collect() |
Manually trigger garbage collection |
LocalReader<T>Thread-local read handle.
| Method | Description |
|---|---|
load() -> ReadGuard<T> |
Read current value, returns RAII guard |
load_cloned() -> T |
Load and clone the current value (requires T: Clone) |
map<F, U>(f: F) -> U |
Apply function to value and return result |
filter<F>(f: F) -> Option<ReadGuard<T>> |
Conditionally return a guard |
is_pinned() -> bool |
Check if this reader is currently pinned |
version() -> usize |
Get current global version |
clone() |
Create a new LocalReader |
share() -> SmrReader<T> |
Create a shareable reader factory |
into_swmr() -> SmrReader<T> |
Convert to a shareable reader factory |
SmrReader<T>A sharable factory for LocalReader.
| Method | Description |
|---|---|
local() -> LocalReader<T> |
Create a LocalReader for the current thread |
clone() |
Clone the factory (Sync + Clone) |
ReadGuard<'a, T>RAII guard, implements Deref<Target = T> and AsRef<T>, protects data from reclamation while guard is alive.
| Method | Description |
|---|---|
version() -> usize |
Get the version this guard is pinned to |
cloned() -> T |
Clone the inner value and return it (requires T: Clone) |
into_inner() -> T |
Consume the guard and return cloned value (requires T: Clone) |
clone() |
Clone the guard (increments pin count) |
| Type | Traits |
|---|---|
SmrSwap<T> |
Default (requires T: Default), From<T>, Debug (requires T: Debug) |
LocalReader<T> |
Clone, Send, Debug |
SmrReader<T> |
Clone, Sync, Send, Debug |
ReadGuard<'a, T> |
Deref, AsRef, Clone, Debug (requires T: Debug) |
Since smr-swap v0.9.0, the default strategy is Write-Preferred. The previous Read-Preferred strategy is available via the read-preferred feature.
Benchmark results comparing SMR-Swap against arc-swap (Windows, Bench mode, Intel Core i9-13900KS).
| Scenario | SMR-Swap (Write-Pref) | SMR-Swap (Read-Pref) | ArcSwap |
|---|---|---|---|
| Single-Thread Read | 4.49 ns | 0.90 ns | 9.19 ns |
| Single-Thread Write | 54.84 ns | 89.81 ns | 104.05 ns |
| Multi-Thread Read (2) | 4.81 ns | 0.90 ns | 9.23 ns |
| Multi-Thread Read (4) | 4.98 ns | 0.92 ns | 9.33 ns |
| Multi-Thread Read (8) | 5.10 ns | 0.94 ns | 9.42 ns |
| Mixed R/W (1W+2R) | 66.10 ns | 86.01 ns | 428.14 ns |
| Mixed R/W (1W+4R) | 71.87 ns | 86.03 ns | 429.16 ns |
| Mixed R/W (1W+8R) | 76.63 ns | 86.75 ns | 470.58 ns |
| Batch Read | 5.52 ns | 1.62 ns | 9.61 ns |
| Read with Held Guard | 55.43 ns | 84.96 ns | 891.63 ns |
| Read Under Memory Pressure | 816.01 ns | 781.29 ns | 1.71 µs |
| R/W Ratio | SMR-Swap (Write-Pref) | SMR-Swap (Read-Pref) | RwLock | Mutex |
|---|---|---|---|---|
| 100:1 | 572.00 ns | 251.49 ns | 5.60 µs | 6.08 µs |
| 10:1 | 144.41 ns | 101.09 ns | 634.45 ns | 713.28 ns |
| 1:1 | 66.59 ns | 87.26 ns | 129.66 ns | 127.39 ns |
| 1:10 | 567.56 ns | 857.83 ns | 238.46 ns | 218.87 ns |
| 1:100 | 5.55 µs | 8.52 µs | 1.05 µs | 970.69 ns |
| Config | SMR-Swap (Write-Pref) | SMR-Swap (Read-Pref) | Mutex | ArcSwap |
|---|---|---|---|---|
| 4W+4R | 506.46 ns | 2.03 µs | 497.52 ns | 1.93 µs |
| 4W+8R | 516.46 ns | 2.10 µs | 818.02 ns | 2.24 µs |
| 4W+16R | 516.20 ns | 2.04 µs | 1.26 µs | 2.93 µs |
| Operation | SMR-Swap (Write-Pref) | SMR-Swap (Read-Pref) | ArcSwap | Mutex |
|---|---|---|---|---|
| Creation | ~152 ns | ~159 ns | ~131 ns | ~49 ns |
| Drop | ~81 ns | ~78 ns | ~108 ns | ~41 ns |
| Handle Clone | ~57 ns | ~57 ns | ~9 ns | ~9 ns |
| Local Check | ~0.18 ns | ~0.18 ns | N/A | N/A |
Write-Preferred (Default):
Read-Preferred (Feature):
Comparison:
SmrSwap<T> holds write capability, not Clone
Mutex<SmrSwap<T>> for multiple writers if neededLocalReader<T> is Send but not Sync
LocalReaderSMR-Swap uses swmr-cell for version-based memory reclamation:
collect() to manually trigger reclamationThis project is licensed under either of
at your option.