Generic Fill Function for Slices of Interfaces and Concrete Types
In Golang, it's possible to define generic functions using generics. When dealing with slices of interfaces and concrete types, a common task is to initialize all elements of the slice with a concrete type. Consider the following function, which aims to fill a slice of pointers to a type X with new instances of X:
<code class="go">func Fill[X any](slice []*X){ for i := range slice { slice[i] = new(X) } }</code>
This function works as expected for slices of any type. However, challenges arise when attempting to extend this behavior to slices of interfaces and specify a concrete type for the element (Y).
<code class="go">func Fill[X, Y any](slice []X){ for i := range slice { slice[i] = new(Y) // not work! } }</code>
When constraining both X and Y to any, the relationship between interfaces and implementers is lost. The compiler treats X and Y as separate types, preventing assignments between them within the function body.
To resolve this, an explicit assertion can be used:
<code class="go">func Fill[X, Y any](slice []X) { for i := range slice { slice[i] = any(*new(Y)).(X) } }</code>
However, this approach may panic if Y does not implement X, as in the case of sync.Mutex (a pointer type) implementing sync.Locker. Additionally, since the zero value for a pointer type is nil, this method does not provide a significant improvement over using make([]X, n), which also initializes the slice with nil values.
A more effective solution is to utilize a constructor function instead of a second type parameter:
<code class="go">func Fill[X any](slice []X, f func() X) { for i := range slice { slice[i] = f() } }</code>
This function takes an additional parameter f, which is a function that returns an instance of X. This allows for more flexibility and supports the filling of interfaces with concrete types. For instance, to initialize a slice of sync.Locker with sync.Mutex elements, the following code can be used:
<code class="go">xs := make([]sync.Locker, 10) Fill(xs, func() sync.Locker { return &sync.Mutex{} })</code>
By utilizing this approach, slices of interfaces can be efficiently filled with instances of a concrete type, providing a convenient and type-safe solution.
The above is the detailed content of How can I efficiently fill a slice of interfaces with instances of a concrete type in Golang, while ensuring type safety and avoiding potential panics?. For more information, please follow other related articles on the PHP Chinese website!