You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform/internal/collections/map.go

146 lines
4.7 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package collections
// Set represents an associative array from keys of type K to values of type V.
//
// A caller-provided "key function" defines how to produce a comparable unique
// key for each distinct value of type K.
//
// Map operations are not concurrency-safe. Use external locking if multiple
// goroutines might modify the map concurrently or if one goroutine might
// read a map while another is modifying it.
type Map[K, V any] struct {
elems map[UniqueKey[K]]MapElem[K, V]
key func(K) UniqueKey[K]
}
// MapElem represents a single element of a map.
type MapElem[K, V any] struct {
K K
V V
}
// NewMap constructs a new map whose key type knows how to calculate its own
// unique keys, by implementing [UniqueKeyer] of itself.
func NewMap[K UniqueKeyer[K], V any]() Map[K, V] {
return NewMapFunc[K, V](K.UniqueKey)
}
// NewMapFunc constructs a new map with the given "map function".
//
// A valid key function must produce only values of types that can be compared
// for equality using the Go == operator, and must guarantee that each unique
// value of K has a corresponding key that uniquely identifies it. The
// implementer of the key function can decide what constitutes a
// "unique value of K", based on the meaning of type K.
//
// Type V is unconstrained by the arguments, so callers must explicitly provide
// the key and value type arguments when calling this function.
func NewMapFunc[K, V any](keyFunc func(K) UniqueKey[K]) Map[K, V] {
return Map[K, V]{
elems: make(map[UniqueKey[K]]MapElem[K, V]),
key: keyFunc,
}
}
// NewMapCmp constructs a new set for any comparable key type, using the
// built-in == operator as the definition of key equivalence.
//
// This is here for completeness in case it's useful when talking to a
// generalized API that operates on maps of any key type, but if your
// key type is fixed and known to be comparable then it's pointless to
// use [Map]; use Go's built-in map types instead, which will then avoid
// redundantly storing the keys twice.
func NewMapCmp[K comparable, V any]() Map[K, V] {
return NewMapFunc[K, V](cmpUniqueKeyFunc[K])
}
// HasKey returns true if the map has an element with the given key, or
// false otherwise.
func (m Map[K, V]) HasKey(k K) bool {
if m.key == nil {
return false // an uninitialized map has no keys
}
uniq := m.key(k)
_, exists := m.elems[uniq]
return exists
}
// Get retrieves the value associated with the given key, or the zero value
// of V if no matching element exists in the map.
func (m Map[K, V]) Get(k K) V {
ret, _ := m.GetOk(k)
return ret
}
// GetOk is like [Map.Get] but also returns a second boolean result reporting
// whether a matching element was present in the map.
func (m Map[K, V]) GetOk(k K) (V, bool) {
if m.key == nil {
var zero V
return zero, false // an uninitialized map has no keys
}
uniq := m.key(k)
ret, ok := m.elems[uniq]
return ret.V, ok
}
// Put writes a new element into the map, with the given key and value.
//
// If there is already an element with an equivalent key (as determined by the
// set's "key function") then the new element replaces that existing element.
func (m Map[K, V]) Put(k K, v V) {
if m.key == nil {
panic("Put into uninitialized collections.Map")
}
uniq := m.key(k)
m.elems[uniq] = MapElem[K, V]{
K: k,
V: v,
}
}
// Delete removes from the map the element with the given key, or does nothing
// if there is no such element.
func (m Map[K, V]) Delete(k K) {
if m.key == nil {
panic("Delete from uninitialized collections.Map")
}
uniq := m.key(k)
delete(m.elems, uniq)
}
// Elems exposes the internal underlying representation of the map directly,
// as a pragmatic compromise for efficient iteration.
//
// The result of this function is part of the internal state of the receiver
// and so callers MUST NOT modify it. If a caller is using locks to ensure
// safe concurrent access then any reads of the resulting map must be
// guarded by the same lock as would be used for other methods that read
// data from the reciever.
//
// The only correct use of this function is as part of a "for ... range"
// statement using only the values of the resulting map:
//
// for _, elem := range map.Elems() {
// k := elem.K
// v := elem.V
// // ...
// }
//
// Do not access or make any assumptions about the keys of the resulting
// map. Their exact values are an implementation detail of the receiver.
func (m Map[K, V]) Elems() map[UniqueKey[K]]MapElem[K, V] {
// This is regrettable but the only viable way to support efficient
// iteration over map elements until Go gains support for range
// loops over custom iterator functions.
return m.elems
}
// Len returns the number of elements in the map.
func (m Map[K, V]) Len() int {
return len(m.elems)
}