safemap
API
safemap
packageAPI reference for the safemap
package.
S
struct
Map
Map is a generic thread-safe map.
It uses a sync.RWMutex to guard access to the underlying map.
pkg/safemap/map.go:13-16
type Map struct
Example
m := safemap.New[string, int]()
m.Set("key", 42)
Fields
| Name | Type | Description |
|---|---|---|
| data | map[K]V | |
| mu | sync.RWMutex |
F
function
New
New creates a new empty SafeMap.
Returns
*Map[K,
V]
pkg/safemap/map.go:19-23
func New[K comparable, V any]() *Map[K, V]
{
return &Map[K, V]{
data: make(map[K]V),
}
}
F
function
TestMap_Basic
Parameters
t
pkg/safemap/map_test.go:8-29
func TestMap_Basic(t *testing.T)
{
m := New[string, int]()
m.Set("a", 1)
m.Set("b", 2)
if v, ok := m.Get("a"); !ok || v != 1 {
t.Errorf("Get a: got %d, want 1", v)
}
if !m.Has("b") {
t.Error("Has b: should be true")
}
if m.Has("c") {
t.Error("Has c: should be false")
}
if m.Len() != 2 {
t.Errorf("Len: got %d, want 2", m.Len())
}
}
F
function
TestMap_Delete
Parameters
t
pkg/safemap/map_test.go:31-39
func TestMap_Delete(t *testing.T)
{
m := New[string, string]()
m.Set("key", "value")
m.Delete("key")
if m.Has("key") {
t.Error("key should be deleted")
}
}
F
function
TestMap_Clear
Parameters
t
pkg/safemap/map_test.go:41-50
func TestMap_Clear(t *testing.T)
{
m := New[int, int]()
m.Set(1, 100)
m.Set(2, 200)
m.Clear()
if m.Len() != 0 {
t.Errorf("Clear: Len should be 0, got %d", m.Len())
}
}
F
function
TestMap_Keys
Parameters
t
pkg/safemap/map_test.go:52-61
func TestMap_Keys(t *testing.T)
{
m := New[string, int]()
m.Set("x", 1)
m.Set("y", 2)
keys := m.Keys()
if len(keys) != 2 {
t.Errorf("Keys: got %d, want 2", len(keys))
}
}
F
function
TestMap_Values
Parameters
t
pkg/safemap/map_test.go:63-72
func TestMap_Values(t *testing.T)
{
m := New[string, int]()
m.Set("x", 10)
m.Set("y", 20)
vals := m.Values()
if len(vals) != 2 {
t.Errorf("Values: got %d, want 2", len(vals))
}
}
F
function
TestMap_Range
Parameters
t
pkg/safemap/map_test.go:74-89
func TestMap_Range(t *testing.T)
{
m := New[string, int]()
m.Set("a", 1)
m.Set("b", 2)
m.Set("c", 3)
count := 0
m.Range(func(k string, v int) bool {
count++
return count < 2
})
if count != 2 {
t.Errorf("Range should stop early, got %d iterations", count)
}
}
F
function
TestMap_GetOrSet
Parameters
t
pkg/safemap/map_test.go:91-103
func TestMap_GetOrSet(t *testing.T)
{
m := New[string, int]()
v1 := m.GetOrSet("key", 42)
if v1 != 42 {
t.Errorf("GetOrSet first call: got %d, want 42", v1)
}
v2 := m.GetOrSet("key", 100)
if v2 != 42 {
t.Errorf("GetOrSet second call: got %d, want 42", v2)
}
}
F
function
TestMap_Compute
Parameters
t
pkg/safemap/map_test.go:105-123
func TestMap_Compute(t *testing.T)
{
m := New[string, int]()
m.Compute("counter", func(v int, exists bool) int {
return v + 1
})
if v, _ := m.Get("counter"); v != 1 {
t.Errorf("Compute first: got %d, want 1", v)
}
m.Compute("counter", func(v int, exists bool) int {
return v + 1
})
if v, _ := m.Get("counter"); v != 2 {
t.Errorf("Compute second: got %d, want 2", v)
}
}
F
function
TestMap_Concurrent
Parameters
t
pkg/safemap/map_test.go:125-144
func TestMap_Concurrent(t *testing.T)
{
m := New[int, int]()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
m.Set(n, n*10)
m.Get(n)
m.Has(n)
}(i)
}
wg.Wait()
if m.Len() != 100 {
t.Errorf("Concurrent: got %d items, want 100", m.Len())
}
}
T
type
Hasher
Hasher is a function that returns a hash for a key.
pkg/safemap/sharded.go:9-9
type Hasher func(K) uint64
S
struct
ShardedMap
ShardedMap is a thread-safe map partitioned into multiple shards to reduce lock contention.
pkg/safemap/sharded.go:12-16
type ShardedMap struct
Fields
| Name | Type | Description |
|---|---|---|
| shards | []*Map[K, V] | |
| mask | uint64 | |
| hasher | Hasher[K] |
F
function
NewSharded
NewSharded creates a new ShardedMap.
shardCount must be a power of 2. If not, it will be rounded up to the next power of 2.
Default shardCount is 32 if input <= 0.
Parameters
hasher
Hasher[K]
shardCount
int
Returns
*ShardedMap[K,
V]
pkg/safemap/sharded.go:22-41
func NewSharded[K comparable, V any](hasher Hasher[K], shardCount int) *ShardedMap[K, V]
{
if shardCount <= 0 {
shardCount = 32
}
// Ensure power of 2
if bits.OnesCount(uint(shardCount)) != 1 {
shardCount = 1 << bits.Len(uint(shardCount))
}
shards := make([]*Map[K, V], shardCount)
for i := 0; i < shardCount; i++ {
shards[i] = New[K, V]()
}
return &ShardedMap[K, V]{
shards: shards,
mask: uint64(shardCount - 1),
hasher: hasher,
}
}
F
function
StringHasher
StringHasher returns a hasher for string keys.
Parameters
s
string
Returns
uint64
pkg/safemap/sharded.go:114-118
func StringHasher(s string) uint64
{
h := fnv.New64a()
h.Write([]byte(s))
return h.Sum64()
}
F
function
TestShardedMap_Basic
Parameters
t
pkg/safemap/sharded_test.go:11-46
func TestShardedMap_Basic(t *testing.T)
{
m := NewSharded[string, int](StringHasher, 4)
m.Set("foo", 1)
m.Set("bar", 2)
if v, ok := m.Get("foo"); !ok || v != 1 {
t.Errorf("Get(foo) = %v, %v; want 1, true", v, ok)
}
if v, ok := m.Get("bar"); !ok || v != 2 {
t.Errorf("Get(bar) = %v, %v; want 2, true", v, ok)
}
if _, ok := m.Get("baz"); ok {
t.Errorf("Get(baz) = _, true; want false")
}
if !m.Has("foo") {
t.Errorf("Has(foo) = false; want true")
}
m.Delete("foo")
if m.Has("foo") {
t.Errorf("Has(foo) after delete = true; want false")
}
if m.Len() != 1 {
t.Errorf("Len() = %d; want 1", m.Len())
}
m.Clear()
if m.Len() != 0 {
t.Errorf("Len() after Clear = %d; want 0", m.Len())
}
}
F
function
TestShardedMap_Range
Parameters
t
pkg/safemap/sharded_test.go:48-64
func TestShardedMap_Range(t *testing.T)
{
m := NewSharded[string, int](StringHasher, 4)
count := 100
for i := 0; i < count; i++ {
m.Set(fmt.Sprintf("key-%d", i), i)
}
seen := 0
m.Range(func(k string, v int) bool {
seen++
return true
})
if seen != count {
t.Errorf("Range visited %d items; want %d", seen, count)
}
}
F
function
TestShardedMap_Concurrent
Parameters
t
pkg/safemap/sharded_test.go:66-92
func TestShardedMap_Concurrent(t *testing.T)
{
m := NewSharded[string, int](StringHasher, 16)
var wg sync.WaitGroup
workers := 10
ops := 1000
for i := 0; i < workers; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < ops; j++ {
key := fmt.Sprintf("key-%d-%d", id, j)
m.Set(key, j) // Write
// Read same key or random key
rKey := fmt.Sprintf("key-%d-%d", rand.Intn(workers), rand.Intn(ops))
m.Get(rKey)
if j%10 == 0 {
m.Delete(key)
}
}
}(i)
}
wg.Wait()
}
F
function
TestShardedMap_GetOrSet
Parameters
t
pkg/safemap/sharded_test.go:94-105
func TestShardedMap_GetOrSet(t *testing.T)
{
m := NewSharded[string, int](StringHasher, 4)
v := m.GetOrSet("foo", 10)
if v != 10 {
t.Errorf("GetOrSet(foo) = %d; want 10", v)
}
v = m.GetOrSet("foo", 20)
if v != 10 {
t.Errorf("GetOrSet(foo) existing = %d; want 10", v)
}
}
F
function
TestShardedMap_Compute
Parameters
t
pkg/safemap/sharded_test.go:107-129
func TestShardedMap_Compute(t *testing.T)
{
m := NewSharded[string, int](StringHasher, 4)
m.Set("foo", 1)
v := m.Compute("foo", func(existing int, exists bool) int {
return existing + 1
})
if v != 2 {
t.Errorf("Compute(foo) = %d; want 2", v)
}
v = m.Compute("bar", func(existing int, exists bool) int {
if !exists {
return 5
}
return existing
})
if v != 5 {
t.Errorf("Compute(bar) = %d; want 5", v)
}
}
F
function
TestShardedMap_KeysDistribution
Parameters
t
pkg/safemap/sharded_test.go:131-147
func TestShardedMap_KeysDistribution(t *testing.T)
{
// Verify that keys are actually distributed across shards
// This uses internal knowledge check
m := NewSharded[string, int](StringHasher, 4)
count := 1000
for i := 0; i < count; i++ {
m.Set(strconv.Itoa(i), i)
}
// Each shard should have roughly count/4 items
// Not strict check due to potential collision, but shouldn't be 0
for i, shard := range m.shards {
if shard.Len() == 0 {
t.Errorf("Shard %d is empty, bad distribution or small sample", i)
}
}
}