Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The real problem is that Go produces interface implementations dynamically.

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.

 help



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: