In general don’t replace interfaces with type parameters when the interface is used to abstract behavior and the dynamic types are actually irrelevant within the function body.
As for usage, it may not change much at call site. Either way you can pass into the function argument only something that implements io.ByteWriter, just like you do without type parameters.
The differences become relevant when the dynamic types of the interface are interesting. The static type of an io.ByteWriter argument is just io.ByteWriter, and to retrieve the dynamic type you have to use an assertion w.(*bytes.Buffer) which may panic, or a type switch; whereas with type parameters, the function deals directly with the concrete type W.
There’s at least two use cases for this. Within the function body:
- you happen to declare new values of those concrete types
- you happen to do comparisons
io.ByteWriter is a bad example in either case, because you are going to use that one precisely for abstracting some behavior rather than for its dynamic type, so that’s not a good candidate for type parametrization.
There may be other situations like pre-Go1.18 "generic" code based on interface{}, or perhaps protobuffers, where proto.Message is an interface, factory patterns, unit test helpers, etc. where the dynamic types are interesting.
Then, clumsy occurrences of reflect.New or reflect.Zero to create new values can be replaced by new(T) or var x T. Demo:
func newFrom(v Setter) Setter {
return reflect.Zero(reflect.TypeOf(v)).Interface().(Setter)
}
vs.
func newFrom[T Setter](v T) T {
return *new(T)
}
About comparisons, interfaces do support equality operators == and != natively, but the comparison may just panic if the dynamic values are not comparable. With type parameters instead the comparison between method-only interfaces simply won't compile unless you explicitly add comparable to the constraint, thus improving code safety.
// compiles, might panic
func equal(v, w Setter) bool {
return v == w
}
vs.
// doesn't compile, must add comparable explicitly
func equal[T Setter](v, w T) bool {
return v == w
}
This doesn't mean type parameters are always appropriate for these use cases either, but you may consider them as candidates.