What is the difference between func F(s fmt.Stringer) and func F[T fmt.Stringer](s T)? When does it matter?
Question 113MediumGo 1.22 to 1.25
For a single argument they behave almost the same. The differences show up in three places:
- Same type across parameters and results:
func Pick[T fmt.Stringer](a, b T) Trequiresaandbto have the same concrete type, and it returns that concrete type rather than an interface. - Slices: a
[]MyTypecannot be passed as a[]fmt.Stringer, because the memory layouts differ and a conversion loop would be needed. A generic[]Taccepts it directly. - Boxing: an interface parameter stores the value in an interface, which may cause it to escape to the heap. A type parameter keeps the concrete value, although the method call may still be indirect (see GC shape stenciling).
type ID int
func (i ID) String() string { return fmt.Sprintf("ID-%d", int(i)) }
func JoinI(xs []fmt.Stringer) string { /* ... */ return "" }
func JoinG[T fmt.Stringer](xs []T) string {
var b strings.Builder
for _, x := range xs {
b.WriteString(x.String())
}
return b.String()
}
ids := []ID{1, 2}
// JoinI(ids) // compile error: []ID is not []fmt.Stringer
fmt.Println(JoinG(ids)) // ID-1ID-2
Rule of thumb: if you only call methods on one value, use a plain interface parameter because it is simpler.
More on Generics
- Q112What problem do type parameters solve that
interface{}/anyparameters did not? - Q114What is a constraint in Go generics? Show the different ways to write one, including the
[P *C]parsing gotcha. - Q115What does the
~(tilde) token mean in a constraint? What happens without it? - Q116Explain type sets. How do unions and intersections combine, and which interfaces can only be used as constraints?
- Q117What is
comparable? What changed in Go 1.20, and what does this program print? - Q118Which operations can you perform on a value of type parameter type? Why can't you access a struct field even when every type in the set has it?