smap

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2025 License: Apache-2.0 Imports: 6 Imported by: 2

README

Go Reference Go Report Card GitHub Release Coverage

smap

Concurrent Safe Generic golang map with optionally sorted iteration (provides go1.24 iterators). Zero dependencies.

See https://pkg.go.dev/fortio.org/smap

History

This was originally developed for fortio.org/tsync which uses and battle tests it including for race conditions (there aren't any!).

Documentation

Overview

Package smap is a zero dependencies library that provides a generic, concurrent safe map with go1.24 iterators including optionally ordered keys iteration.

This was originally developed for fortio.org/tsync which uses and battle tests it including for race conditions (there aren't any!).

Example

Example demonstrates basic usage of the concurrent safe Map including creating, setting, getting, and iterating with All(). The whole point though would be do to these operations concurrently from multiple goroutines.

// Create a new concurrent safe map
m := New[string, int]()

// Add some entries
m.Set("apple", 5)
m.Set("banana", 3)
m.Set("cherry", 8)

// Get a specific value
count, exists := m.Get("banana")
if exists {
	fmt.Printf("Bananas: %d\n", count)
}

// Check the total count
fmt.Printf("Total items: %d\n", m.Len())

// Check if a key exists
fmt.Printf("Has apple: %t\n", m.Has("apple"))

// Iterate over all entries using range
// Note: We collect and sort for deterministic output in this example
var toDelete []string
// Using m.All you can't mutate the map during iteration (use AllSorted or collect changes first, or
// use KeysSnapshot or KeysValuesSnapshot first then mutate)
for fruit, count := range m.All() {
	if count < 8 {
		toDelete = append(toDelete, fruit)
	}
}
m.Delete(toDelete...) // Delete multiple, after iteration
fmt.Printf("After removing items with count < 8, total items: %d\n", m.Len())
fmt.Printf("Map is now just %v\n", m)
Output:
Bananas: 3
Total items: 3
Has apple: true
After removing items with count < 8, total items: 1
Map is now just map[cherry:8]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NaturalSort

func NaturalSort[Q cmp.Ordered, V any](s *Map[Q, V]) iter.Seq2[Q, V]

NaturalSort returns an iterator that visits key-value pairs in the natural order of Q (using <). This requires Q (K from the Map[Q, V]) to be an ordered type.

Example

ExampleNaturalSort demonstrates sorting with naturally ordered types.

// Create a map with ordered keys (strings, ints, etc.)
scores := New[string, int]()
scores.Set("Charlie", 85)
scores.Set("Alice", 92)
scores.Set("Bob", 78)

// Iterate in natural (alphabetical) order
fmt.Println("Scores (alphabetically):")
for name, score := range NaturalSort(scores) {
	fmt.Printf("  %s: %d\n", name, score)
}
Output:
Scores (alphabetically):
  Alice: 92
  Bob: 78
  Charlie: 85

Types

type KV

type KV[K comparable, V any] struct {
	Key   K
	Value V
}

KV is a key-value pair used for Map.SetBatch and returned by Map.KeysValuesSnapshot (optional).

type Map

type Map[K comparable, V any] struct {
	// contains filtered or unexported fields
}

Map is a concurrent safe map.

func FromMap added in v1.0.0

func FromMap[K comparable, V any](m map[K]V) *Map[K, V]

FromMap creates a new Map from the provided standard map by cloning its contents. Passing a nil map results in a Map with a nil underlying map which will not work.

func New

func New[K comparable, V any]() *Map[K, V]

New creates a new concurrent safe Map.

func Transfer added in v1.0.0

func Transfer[K comparable, V any](m map[K]V) *Map[K, V]

Transfer moves the input map into a new concurrent safe Map. You must not use the input map after calling Transfer or it would defeat the concurrent safety. In most case use FromMap instead to clone the input map and not take ownership of it. Passing a nil map results in a Map with a nil underlying map which will not work.

func (*Map[K, V]) All

func (s *Map[K, V]) All() iter.Seq2[K, V]

All returns an iterator over key-value pairs from the map. This allows ranging over the sync Map like a regular map using Go 1.24+ iterators. The iteration takes a read lock for the duration of going over the entries. If you wish to modify the map during iteration, you should postpone to after the loop or use Map.AllSorted or NaturalSort which are doing 2 phases and the 2nd phase is without holding a lock. If using this one and wanting to mutate the map, accumulate entries in a slice and call Delete(toDeleteSlice...) or Map.SetBatch for instance.

func (*Map[K, V]) AllSorted

func (s *Map[K, V]) AllSorted(less func(a, b K) bool) iter.Seq2[K, V]

AllSorted returns an iterator over key-value pairs where keys are visited in the order defined by less. Unlike Map.All only the keys snapshot occurs under a read lock, then sorting and value lookups happen without holding it. Because of that, by the time a key is revisited later, it may have been deleted; such entries are skipped.

Example

ExampleMap_AllSorted demonstrates using AllSorted with a custom struct to iterate over entries in a specific order.

// Define a custom struct with multiple fields
type Task struct {
	Name     string
	Priority int
}

// Create a map with Task keys
m := New[Task, string]()

// Add some tasks
m.Set(Task{Name: "Fix bug", Priority: 1}, "In Progress")
m.Set(Task{Name: "Write docs", Priority: 3}, "Not Started")
m.Set(Task{Name: "Review PR", Priority: 2}, "Completed")
m.Set(Task{Name: "Deploy", Priority: 1}, "Pending")

// Iterate in priority order (lowest priority number first)
// If priorities are equal, sort by name
fmt.Println("Tasks by priority:")
for task, status := range m.AllSorted(func(a, b Task) bool {
	if a.Priority != b.Priority {
		return a.Priority < b.Priority
	}
	return a.Name < b.Name
}) {
	// Here it's ok to mutate during iteration:
	if task.Priority == 1 {
		m.Set(task, "Changed") // Update high priority tasks
	}
	newStatus, _ := m.Get(task)
	fmt.Printf("  [P%d] %s: %s (current: %s)\n", task.Priority, task.Name, status, newStatus)
}
Output:
Tasks by priority:
  [P1] Deploy: Pending (current: Changed)
  [P1] Fix bug: In Progress (current: Changed)
  [P2] Review PR: Completed (current: Completed)
  [P3] Write docs: Not Started (current: Not Started)

func (*Map[K, V]) Clear

func (s *Map[K, V]) Clear() (newVersion uint64)

Clear removes all entries from the map.

func (*Map[K, V]) Clone added in v1.0.0

func (s *Map[K, V]) Clone() *Map[K, V]

Clone creates a copy of the Map.

func (*Map[K, V]) Copy added in v1.0.0

func (s *Map[K, V]) Copy(src *Map[K, V]) (newVersion uint64)

Copy copies all key/value pairs from src adding them to the current map. When a key in src is already present in this map, the value will be overwritten by the value associated with the key in src. Read lock is acquired on src and write lock on this map during the operation. The new version of the map is returned (previous + 1). Because of this calling Copy in both directions between 2 maps would lead to a deadlock.

func (*Map[K, V]) Delete

func (s *Map[K, V]) Delete(key ...K) (newVersion uint64)

Delete removes the given keys from the map and returns the new version of the map.

func (*Map[K, V]) Get

func (s *Map[K, V]) Get(key K) (V, bool)

Get retrieves the value for the given key.

func (*Map[K, V]) GoString added in v1.0.0

func (s *Map[K, V]) GoString() (debug string)

GoString() returns a string representation of the map for debugging purposes (%#v).

func (*Map[K, V]) Has

func (s *Map[K, V]) Has(key K) bool

Has checks if the given key exists in the map.

func (*Map[K, V]) Keys

func (s *Map[K, V]) Keys() iter.Seq[K]

Keys returns an iterator over keys from the map. This allows ranging over just the keys using Go 1.24+ iterators. Like Map.All this iterator takes a read lock for the duration of going over the entries.

func (*Map[K, V]) KeysSnapshot added in v1.0.0

func (s *Map[K, V]) KeysSnapshot() []K

KeysSnapshot returns a snapshot slice of the current keys in the map. The snapshot is taken under a read lock.

func (*Map[K, V]) KeysSorted

func (s *Map[K, V]) KeysSorted(less func(a, b K) bool) iter.Seq[K]

KeysSorted returns an iterator over keys sorted using the provided comparison function. Unlike Map.Keys, the map snapshot occurs under a read lock, but then sorting happens without holding the lock.

func (*Map[K, V]) KeysValuesSnapshot added in v1.0.0

func (s *Map[K, V]) KeysValuesSnapshot() []KV[K, V]

KeysValuesSnapshot returns a snapshot slice of the current key-value pairs in the map. The snapshot is taken under a read lock.

func (*Map[K, V]) Len

func (s *Map[K, V]) Len() int

Len returns the number of entries in the map.

func (*Map[K, V]) Set

func (s *Map[K, V]) Set(key K, value V) (newVersion uint64)

Set sets the value for the given key and returns the new version of the map.

func (*Map[K, V]) SetBatch added in v1.0.0

func (s *Map[K, V]) SetBatch(kvs []KV[K, V]) (newVersion uint64)

SetBatch sets multiple key-value pairs in a single lock/unlock batch (and returns the new version of the map).

func (*Map[K, V]) String added in v1.0.0

func (s *Map[K, V]) String() (debug string)

String() returns a string representation of the map for debugging purposes (%s/%v).

func (*Map[K, V]) Transaction added in v1.1.0

func (s *Map[K, V]) Transaction(fn func(m map[K]V)) (newVersion uint64)

Transaction executes the provided function fn within a write lock, with access to the internal map.

func (*Map[K, V]) Values

func (s *Map[K, V]) Values() iter.Seq[V]

Values returns an iterator over values from the map. This allows ranging over just the values using Go 1.24+ iterators. Like Map.All this iterator takes a read lock for the duration of going over the entries.

func (*Map[K, V]) Version

func (s *Map[K, V]) Version() (current uint64)

Version returns the current version of the map. If any changes were made, the version is incremented by 1 each time.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL