More specifically, it is that it would introduce surprising performance cliffs – code becoming surprisingly slow due to seemingly unrelated changes.
Though BTQH I think an even more important argument is that you would need to have effectively two generics implementations, one working at runtime and one working at compile time. That's a lot of complexity, with surprising failure modes if these two are not bug-compatible.
> But that runtime reflection is exactly how you would work around it today.
I think the overwhelming majority of people will "work around it" by just not trying to use generic methods.
My understanding is that go already has a hybrid system works at compiletime and sometimes at runtime.
My point is for interface generics it could just always use a single instantiation. Similar to what java does.
Or alternatively, go could go the other direction and add a new type of interface that is only for use in generic constraints, and then generic methods could be part of that interface, but not normal interfaces, so that the generic methods could be called from other generic functions. That would be similar to rust and c++.
Can't speak too deeply for Go specifically, but I do know on .NET one of the big reasons generic methods where T is a structure gets monomorphized per type, is so that stack size is adjusted and potentially even arg passing (i.e. large struct) as far as the caller/callee.
The determination wether type T implements interface I is made at runtime. So is generation of the necessary vtables to produce the interface implementation.
So you can do things like this in package a:
type S struct {
//...
}
func (s \* S) Foo() {
//..
}
and something like this in package b: type Foo interface {
Foo()
}
func DoSomethingWithAFoo(f Foo) {
}
and something like this in package c: func Stuff(obj any) {
theFoo, _ := obj.(b.Foo)
theFoo.Foo()
}
And then do: var s a.S
c.Stuff(s)
And everything works.For generic functions, go uses a strategy similar to C++ templates: when you call a generic function the compiler statically produces a concrete specialization of the generic function based on the inferred types for generic parameters.
That is, if you do:
func Bar[T any](x T) {
//...
}
And you do: var x int
var y string
var z float64
Bar(x)
Bar(y)
Bar(z)
The compiler statically generates 3 versions of Bar, one that takes an int, one that takes a string, and another that takes a float64.These two things don't work well together. If I have a variable typed as `any`, and I want to cast that to an interface, I need to dynamically determine 2 things:
1. The shape of the interface's vtable. The go runtime does this by iterating over the runtime metadata for the interface type.
2. For each named method in the interface's vtable, the address of the concrete function to stick in that vtable slot. This is done by accessing the reflection metadata for the implementing type. It verfies the method with name X for type T matches the required signature for the method with name X for interface I, then sticks that method pointer into the appropriate vtable slot.
The problem, however, is what happens when the method with name X is generic. There may, or may not, be an actual concrete method for the set of type parameters. It's possible that statically type T does implement interface I (via generic methods) but that dynamically it doesn't because the particular generic instantiation needed for the particular interface was never made statically.
Prior to go 1.27, this was never an issue, because methods could not declare their own type parameters. They could reference the generic parameters of the receiver, but once the receiver type was known, there was only ever one concrete method X for that receiver.
Once you allow methods to have their own generic type parameters, the compiler can introduced several different concrete implementations for a method X.
This is ok, when you do somethnig like:
var x SomethingWithGenericMethods
x.Foo(1)
x.Foo("hello")
x.Foo(1.2)
Because the compiler knows statically from the Foo call sites which concrete methods it needs to generate.But, when you introduce a dynamic cast:
var x SomethingWithGenericMethods
var i SomeInterface
i = x.(any).(SomeInterface)
i.Foo(1)
i.Foo("Hello")
i.Foo(1.2)
It's entirely possible that the necessary Foo implementations don't actually exist in the binary.So, go 1.27 introduces generic methods, but it gets around this problem by saying:
1. Interface types can't define generic methods
2. Generic methods can't be used to implement interfaces
Thus, it allows adding generic methods without introducing the issues that crop up with dynamic interface implementations.