Rob Pike wrote a bit about it in "less is exponentially more". He points out that generics are used by languages that focus on types and that Go focuses on composition and uses interfaces ( and language buildins ) instead of types to solve problems.
> generics are used by languages that focus on types and that Go focuses on composition and uses interfaces ( and language buildins ) instead of types to solve problems.
Interfaces are (abstract) types, so you can't “use interfaces instead of types”. Go’s use of interfaces is a variation of the “you can only have subtypes of abstract types” approach. Julia is an example of another modern language which also uses this general approach to type heirarchy, which, yes, does correspond to a strong preference for composition over inheritance; OTOH, like most relatively modern non-Go language with this approach, it also has generics. The idea tha generics are mostly used by languages that don't follow this approach is true in the sense that most languages, and therefore most with generics, do not follow this approach. But modern languages besides Go that follow the “composition-over-inheritance, no subtyping concrete types” approach seem to be just as likely as languages which support subtyping concrete types to have generics.
So, as well as not actually explaining anything (generics and composition-over-inheritance don't solve the same problem, so using the latter doesn't explain not using the former even if they did tend coincide in practice), it's doesn't seem to point to a real correlation.
How is this false? I agree with this assertion after having used Go extensively for some time. Where Go really shines is when the stdlib offers a nice abstraction (io.Reader, io.Writer, http.Handler) and you can compose your programs from building blocks that use these abstractions. For example, from the http.Handler interface follows directly a middleware interface:
type Middleware interface {
Wrap(http.Handler) http.Handler
}
without requiring any generic types. For comparison, the same thing in Rust would rely on type polymorphism:
//assuming that Handler is a trait
trait Middleware<T: Handler> {
type Result: Handler;
fn Wrap(handler: T) -> Result;
}
//NOTE: Not using impl-trait here to make it clear that
//two type variables are involved (one type parameter
//and one associated type).
You could mimic the Go behavior if you box all your traits, which Rust does not like to do because it's not zero-cost: