I opened an issue to start a discussion around this, but perhaps it was the 
wrong place, and it is better to begin with email- though formatting seems 
quite a bit worse over email.

https://github.com/golang/go/issues/74916

Here is the relevant contents of that issue:

Preface

This topic has almost certainly been covered elsewhere; if that is the 
case, please direct me to the appropriate discussion and I'll read through 
it. I did try looking for something that covers this explicitly, and while 
I found somewhat similar discussions (such as those around creating 
discriminated unions), I didn't find this exact one.
Overview

TLDR; Disallowing the use of uninstantiated types as function parameters 
reduces clarity, is surprising, and leads to bugs. Allowing them improves 
communication within the codebase.

Definition of instantiation, from 
https://go.dev/blog/intro-generics#type-parameters

Providing the type argument to GMin, in this case int, is called 
instantiation. Instantiation happens in two steps. First, the compiler 
substitutes all type arguments for their respective type parameters 
throughout the generic function or type. Second, the compiler verifies that 
each type argument satisfies the respective constraint.

This process is required before a type may be used in a function 
definition. This restricts the ability of the programmer to communicate 
effectively the behaviors of their functions, and makes the use of generics 
as function argument types an all-or-nothing proposition: either the 
function must be "concrete"- that is, instantiated- or any must be used.
Example

Take the following example:
package main import "fmt" type Test[T any] interface { Get() T } type 
TestImpl[T any] struct { value T } func (t *TestImpl[T]) Get() T { return 
t.value } // This is the problem func Print(t Test) { switch t := t.(type) 
{ case Test[string]: fmt.Println(t.Get()) default: fmt.Println("not a 
supported type") } } func main() { Print(&TestImpl[string]{value: "test"}) 
} 

This will not compile, resulting in cmd/types/main.go:17:14: cannot use 
generic type Test[T any] without instantiation.

Instead, the programmer is left with 2 options:
Option 1

The programmer may "color" the print function (if you're unfamiliar, it's 
the concept presented here 
<https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/>
 but applied more generally).
func Print[T](t Test[T]) { // .... } 

This doesn't really "solve" anything, it means the caller is now faced with 
the same problem. Worse, if the Print function is attached to a struct, the 
struct must also be genericized, and the lack of union types means that it 
can't necessarily be done properly.

Consider a struct that can print Test things:
type Printer[T] struct {} func (p *Printer[T]) Print(t Test[T]) { 
fmt.Println(t.Get()) } func main() { stringPrinter := Printer[string]{} 
stringPrinter.Print(&TestImpl[string]{value: "test"}) intPrinter := 
Printer[int]{} intPrinter.Print(&TestImpl[int]{value: 1}) } 

Suddenly we need to create a new printer for every type. If the purpose of 
printer is to print Test things, this approach does not work.
Option 2

Use any as the type parameter.
func Print(t any) { switch t := t.(type) { case Test[string]: 
fmt.Println(t.Get()) default: fmt.Println("not a supported type") } } func 
main() { Print(&TestImpl[string]{value: "test"}) } 

This is the common approach. The issues is that the any type communicates 
nothing, and provides no compiler guarantees. The programmer cannot 
communicate to the caller the expectations of the function, or even the 
approximate expectations.
Proposal

If the compiler allowed the use of uninstantiated types as function 
parameters, the programmer could better communicate the expectations of the 
function to the caller.
func Print(t Test) { switch t := t.(type) { case Test[string]: 
fmt.Println(t.Get()) default: fmt.Println("not a supported type") } } 

This would be backwards compatible since today it would simply result in a 
compile error.
Preemptive objections and responses

*The function parameter still contains no concrete type information, so it 
is not usable by the function code.*

This is true; the programmer would need to use a type assertion to get the 
instantiated type. In that way, it behaves like any does today.

However, the compiler is still more effectively able to bound the inputs.

For example, the compiler could error like it does with other impossible 
type assertions:
type I interface { Get() string } // This is a compiler error in Go func 
Coerce(t I) { t2 := t.(string) // impossible type assertion: t.(string) // 
string does not implement I (missing method Get) } type Test[T any] 
interface { Get() T } // This could be a compiler error func Coerce2(t 
Test) { t2 := t.(string) // impossible type assertion: t.(string) // string 
is not of type Test } 

*This wouldn't work on structs, since type assertions are only allowed on 
interfaces*

Even if the compiler only allowed uninstantiated types for interface 
arguments, this would still be better than just using any today.

It is also not clear to me that this definitely wouldn't be possible for 
structs. In a sense, an uninstantiated generic struct is like an interface, 
so there might be a reasonable way to implement it, something like creating 
implicit interfaces for generic structs. Certainly, from a language 
perspective, it seems like it would be reasonable to allow uninstantiated 
struct types.

*The programmer still needs to type assert within the function, and it may 
be non-exhaustive*

This is no different from using any today, but still communicates more to 
the caller and allows additional compiler checks.

The ability to communicate more precisely add clarity with no additional 
mental cost and is a good thing.
Conclusion

This seems to me like a useful, backwards compatible change.

Because it seems so useful, and because the Go team tends to think very 
carefully about the language, I think I'm probably missing something.

So I would appreciate feedback on this proposal, including any previous 
proposals 
that I may have missed and of course issues with doing this.

-----
Thanks,
John Mark Wilkinson

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/golang-nuts/5393423d-7441-4b9a-a020-4c00eae40e66n%40googlegroups.com.

Reply via email to