Go

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) T requires a and b to have the same concrete type, and it returns that concrete type rather than an interface.
  • Slices: a []MyType cannot be passed as a []fmt.Stringer, because the memory layouts differ and a conversion loop would be needed. A generic []T accepts 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

All 36 Generics questions