Description
Proposal Details
The math/rand/v2 package has been successful. Let's consider another v2 package: sync/v2.
This is an update of #47657.
Background
The current sync package provides Map
and Pool
types. These types were designed before Go supported generics. They work with values of type any
.
Using any
is not compile-time-type-safe. Nothing prevents using a key or value of any arbitrary type with a sync.Map
. Nothing prevents storing a value of any arbitrary type into a sync.Pool
. Go is in general a type-safe language. For all the reasons why Go map and slice types define the their key and element types, sync.Map
and sync.Pool
should as well.
Also, using any
is inefficient. Converting a non-pointer value to any
requires a memory allocation. This means that building a sync.Map
with a key type of string
requires an extra allocation for every value stored in the map. Storing a slice type in a sync.Pool
requires an extra allocation.
These issues are easily avoided by changing sync.Map
and sync.Pool
to be generic types.
While we can't change Map
and Pool
to be generic in the current sync package because of compatibility concerns (see the discussion at #48287), we could consider adding new generic types to the existing sync package, as proposed at #47657. However, that will leave us with a confusing wart in the sync package that we can never remove. Having both sync.Pool
and (say) sync.PoolOf
is confusing. Deprecating sync.Pool
still leaves us with a strange name for the replacement type.
Introducing a sync/v2 package will permit us to easily transition from the current package to a new package. It's true that future users will have to know to import "sync/v2" rather than "sync". However, few people write their own imports these days, and this transition is easily handled by goimports and similar tools.
Proposal
In the sync/v2 package, the following types and functions will be the same as in the current sync package:
func OnceFunc(f func()) func()
func OnceValue[T any](f func() T) func() T
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2)
type Cond struct{ ... }
func NewCond(l Locker) *Cond
type Locker interface{ ... }
type Mutex struct{ ... }
type Once struct{ ... }
type RWMutex struct{ ... }
type WaitGroup struct{ ... }
The existing Map
type will be replaced by a new type that takes two type parameters. Note that the original Range
method is replaced with an All
method that returns an iterator.
// Map is like a Go map[K]V but is safe for concurrent use
// by multiple goroutines without additional locking or coordination.
// ...and so forth
type Map[K comparable, V any] struct { ... }
// Load returns the value stored in the map for a key, or the zero value if no
// value is present.
// The ok result indicates whether value was found in the map.
func (m *Map[K, V]) Load(key K) (value V, ok bool)
// Store sets the value for a key.
func (m *Map[K, V]) Store(key K, value V)
// LoadOrStore returns the existing value for the key if present.
// Otherwise, it stores and returns the given value.
// The loaded result is true if the value was loaded, false if stored.
func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool)
// LoadAndDelete deletes the value for a key, returning the previous value if any.
// The loaded result reports whether the key was present.
func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool)
// Delete deletes the value for a key.
func (m *Map[K, V]) Delete(key K)
// Swap swaps the value for a key and returns the previous value if any.
// The loaded result reports whether the key was present.
func (m *Map[K, V]) Swap(key K, value V) (previous V, loaded bool)
// CompareAndDelete deletes the entry for key if its value is equal to old.
// This panics if V is not a comparable type.
//
// If there is no current value for key in the map, CompareAndDelete
// returns false.
func (m *Map[K, V]) CompareAndDelete(key K, old V) (deleted bool)
// CompareAndSwap swaps the old and new values for key
// if the value stored in the map is equal to old.
// This panics if V is not a comparable type.
func (m *Map[K, V]) CompareAndSwap(key K, old, new V) (swapped bool)
// Clear deletes all the entries, resulting in an empty Map.
func (m *Map[K, V]) Clear()
// All returns an iterator over the keys and values in the map.
// ... and so forth
func (m *Map[K, V]) All() iter.Seq2[K, V]
// TODO: Consider Keys and Values methods that return iterators, like maps.Keys and maps.Values.
The existing Pool
type will be replaced by a new type that takes a type parameter.
2025-01-04: The new version of Pool
does not have an exported New
field; instead, use NewPool
to create a Pool
that calls a function to return new values.
Note that the original Get
method is replaced by one that returns two results, with the second being a bool
indicating whether a value was returned. This is only useful if the New field is optional: we could also change this to require the New field to be set, or to provide a default implementation that returns the zero value of T
.
// A Pool is a set of temporary objects of type T that may be individually saved and retrieved.
// ...and so forth
type Pool[T any] struct {
...
}
// NewPool returns a new pool. If the newf argument is not nil, then when the pool is empty,
// newf is called to fetch a new value. This is useful when the values in the pool should be initialized.
func NewPool[T any] (newf func() T) *Pool[T]
// Put adds x to the pool.
func (p *Pool[T]) Put(x T)
// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// ...and so forth
//
// If Get does not have a value to return, and p was created with a call to [NewPool] with a non-nil argument,
// Get returns the result of calling the function passed to [NewPool].
func (p *Pool[T]) Get() T
Metadata
Metadata
Assignees
Type
Projects
Status