doc/faq: expand discussion of generic methods

Rob Pike pointed out that it could be clearer and more self-contained,
as well as being clear about generic methods never happening.

Change-Id: I854a38e20b4a7cd695bb3fa0a237759c5f7224d3
Reviewed-on: https://go-review.googlesource.com/c/website/+/599475
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Russ Cox 2024-07-18 15:08:20 -04:00 коммит произвёл Gopher Robot
Родитель ed6d739b5c
Коммит 388f3d03b7
1 изменённых файлов: 84 добавлений и 9 удалений

Просмотреть файл

@ -1669,18 +1669,93 @@ code.
Go permits a generic type to have methods, but, other than the
receiver, the arguments to those methods cannot use parameterized
types.
The methods of a type determines the interfaces that the type
implements, but it is not clear how this would work with parameterized
arguments for methods of generic types.
It would require either instantiating functions at run time or
instantiating every generic function for every possible type
argument.
Neither approach seems feasible.
For more details, including an example, see the
[proposal](/design/43651-type-parameters#no-parameterized-methods).
We do not anticipate that Go will ever add generic methods.
The problem is how to implement them.
Specifically, consider checking whether a value in an
interface implements another interface with additional methods.
For example, consider this type, an empty struct with a
generic `Nop` method that returns its argument, for any possible type:
```
type Empty struct{}
func (Empty) Nop[T any](x T) T {
return x
}
```
Now suppose an `Empty` value is stored in an `any` and passed
to other code that checks what it can do:
```
func TryNops(x any) {
if x, ok := x.(interface{ Nop(string) string }); ok {
fmt.Printf("string %s\n", x.Nop("hello"))
}
if x, ok := x.(interface{ Nop(int) int }); ok {
fmt.Printf("int %d\n", x.Nop(42))
}
if x, ok := x.(interface{ Nop(io.Reader) io.Reader }); ok {
data, err := io.ReadAll(x.Nop(strings.NewReader("hello world")))
fmt.Printf("reader %q %v\n", data, err)
}
}
```
How does that code work if `x` is an `Empty`?
It seems that `x` must satisfy all three tests,
along with any other form with any other type.
What code runs when those methods are called?
For non-generic methods, the compiler generates the code
for all method implementations and links them into the final program.
But for generic methods, there can be an infinite number of method
implementations, so a different strategy is needed.
There are four choices:
1. At link time, make a list of all the possible dynamic interface checks,
and then look for types that would satisfy them but are missing
compiled methods, and then reinvoke the compiler to add those methods.
This would make builds significantly slower, by needing to stop after
linking and repeat some compilations. It would especially slow down
incremental builds. Worse, it is possible that the newly compiled method
code would itself have new dynamic interface checks, and the process
would have to be repeated. Examples can be constructed where
the process never even finishes.
2. Implement some kind of JIT, compiling the needed method code at runtime.
Go benefits greatly from the simplicity and predictable performance
of being purely ahead-of-time compiled.
We are reluctant to take on the complexity of a JIT just to implement
one language feature.
3. Arrange to emit a slow fallback for each generic method that uses
a table of functions for every possible language operation on the type parameter,
and then use that fallback implementation for the dynamic tests.
This approach would make a generic method parameterized by an
unexpected type much slower than the same method
parameterized by a type observed at compile time.
This would make performance much less predictable.
4. Define that generic methods cannot be used to satisfy interfaces at all.
Interfaces are an essential part of programming in Go.
Disallowing generic methods from satisfying interfaces is unacceptable
from a design point of view.
None of these choices are good ones, so we chose “none of the above.”
Instead of methods with type parameters, use top-level functions with
type parameters, or add the type parameters to the receiver type.
For more details, including more examples, see the
[proposal](/design/43651-type-parameters#no-parameterized-methods).
### Why can't I use a more specific type for the receiver of a parameterized type? {#types_in_method_declaration}
The method declarations of a generic type are written with a receiver