Skip to content

Commit 3343302

Browse files
committed
Add tests, missing functions
1 parent 15d09bc commit 3343302

4 files changed

Lines changed: 392 additions & 2 deletions

File tree

README.md

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,95 @@
11
# genericsyncmap
2-
A thread-safe, type-safe map for golang that uses generics and locks to provide safety
2+
3+
A typed, thread-safe map implementation for Go, built on top of `sync.Map` using generics.
4+
5+
`genericsyncmap` provides the concurrency benefits of `sync.Map` with the safety and convenience of Go generics. It implements the full feature set of `sync.Map` (as of Go 1.20+), and is as close to a drop-in replacement as possible. You can remove the type casts from your code, and replace them with this.
6+
7+
## Features
8+
9+
- **Type Safety**: No more `interface{}` casting or assertions. Compile-time checks for key and value types.
10+
- **Thread Safety**: Fully safe for concurrent use by multiple goroutines without additional locking.
11+
- **Full Parity**: Implements all methods found in the standard library's `sync.Map`.
12+
- **Zero Dependencies**: Built strictly on the standard library.
13+
14+
## Installation
15+
16+
```bash
17+
go get github.com/donomii/genericsyncmap
18+
```
19+
20+
## Use
21+
22+
### Basic Operations
23+
24+
```go
25+
package main
26+
27+
import (
28+
"fmt"
29+
"github.com/donomii/genericsyncmap"
30+
)
31+
32+
func main() {
33+
// Create a new map with string keys and int values
34+
m := syncmap.NewSyncMap[string, int]()
35+
36+
// Store values
37+
m.Store("apple", 10)
38+
m.Store("banana", 20)
39+
40+
// Load values
41+
if val, ok := m.Load("apple"); ok {
42+
fmt.Printf("Apple count: %d\n", val)
43+
}
44+
45+
// Delete
46+
m.Delete("banana")
47+
}
48+
```
49+
50+
### Atomic Operations
51+
52+
One of the main advantages of `sync.Map` is the ability to perform atomic operations without external locks. `genericsyncmap` exposes all of these in a type-safe way:
53+
54+
```go
55+
// LoadOrStore
56+
val, loaded := m.LoadOrStore("cherry", 5)
57+
58+
// LoadAndDelete
59+
val, loaded = m.LoadAndDelete("apple")
60+
61+
// Swap (Store and return previous)
62+
prev, loaded := m.Swap("grape", 30)
63+
64+
// CompareAndSwap (Swap only if old value matches)
65+
swapped := m.CompareAndSwap("grape", 30, 35)
66+
67+
// CompareAndDelete (Delete only if value matches)
68+
deleted := m.CompareAndDelete("grape", 35)
69+
```
70+
71+
### Iteration
72+
73+
Use the `Range` method to iterate over the map. Returning `false` from the callback stops iteration.
74+
75+
```go
76+
m.Range(func(key string, value int) bool {
77+
fmt.Printf("%s: %d\n", key, value)
78+
return true // continue iteration
79+
})
80+
```
81+
82+
Helper methods `Keys()`, `Values()`, and `Len()` are also provided, though be aware that `Len()` requires iterating the entire map (O(N)).
83+
84+
## Performance
85+
86+
Since this is a thin wrapper around `sync.Map`, performance characteristics are identical:
87+
- Optimized for cases where keys are stable (loaded/read often, updated rarely).
88+
- Excellent for cache implementations.
89+
90+
91+
Benchmarks included in the repository (`go test -bench .`).
92+
93+
## License
94+
95+
MIT

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
module github.com/donomii/clusterF/syncmap
1+
module github.com/donomii/genericsyncmap
22

33
go 1.24.0

syncmap.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,44 @@ func (sm *SyncMap[K, V]) LoadOrStore(key K, value V) (V, bool) {
3333
return actual.(V), loaded
3434
}
3535

36+
// LoadAndDelete gets existing value and deletes it, returns value and whether it was loaded (deleted)
37+
func (sm *SyncMap[K, V]) LoadAndDelete(key K) (V, bool) {
38+
val, loaded := sm.m.LoadAndDelete(key)
39+
if loaded {
40+
return val.(V), true
41+
}
42+
var zero V
43+
return zero, false
44+
}
45+
3646
// Delete removes a key
3747
func (sm *SyncMap[K, V]) Delete(key K) {
3848
sm.m.Delete(key)
3949
}
4050

51+
// Swap swaps the value for a key and returns the previous value if any.
52+
// The loaded result reports whether the key was present.
53+
func (sm *SyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
54+
prev, loaded := sm.m.Swap(key, value)
55+
if loaded {
56+
return prev.(V), true
57+
}
58+
var zero V
59+
return zero, false
60+
}
61+
62+
// CompareAndSwap swaps the old and new values for key if the value stored in the map is equal to old.
63+
// The old value must be of a comparable type.
64+
func (sm *SyncMap[K, V]) CompareAndSwap(key K, old V, new V) (swapped bool) {
65+
return sm.m.CompareAndSwap(key, old, new)
66+
}
67+
68+
// CompareAndDelete deletes the entry for key if its value is equal to old.
69+
// The old value must be of a comparable type.
70+
func (sm *SyncMap[K, V]) CompareAndDelete(key K, old V) (deleted bool) {
71+
return sm.m.CompareAndDelete(key, old)
72+
}
73+
4174
// Range calls fn for each key-value pair. Returning false quits the iteration
4275
func (sm *SyncMap[K, V]) Range(fn func(key K, value V) bool) {
4376
sm.m.Range(func(key, value any) bool {

0 commit comments

Comments
 (0)