I'm intentionally not touching on Ian's replies because I don't really have 
the background knowledge here to add anything in that direction, but I do 
have something to add that might help unblock you today with a compromise 
option.

I have an Option 3 which has served me well for situations like this. 
Remember that type parameters and interfaces are (at least, theoretically) 
orthogonal; you should use both in this case! It simply turns it into the 
other standing question of "Why doesn't Go have sum types?" or "How do I 
create a sealed interface with no external implementations?" Basically, for 
a generic `Test[T]` type, create a `AnyTest` type that has a hidden method 
that only the real Test type implements. 
e.g. https://play.golang.com/p/eUDLalvEpbg . It can be especially powerful 
when you don't even need to type-assert to Test explicitly - e.g. if you 
have some useful methods on type Test then you actually can just 
type-assert to some interface type with the method you want, rather than 
needing to go all the way back to a concrete struct type to access the data 
in a type-safe way.

Note that you can still bypass this by embedding an arbitrary 
Test[whatever] in a struct, since that will promote the unexported `isTest` 
method; you can get around this using some tricks that have runtime cost 
(https://play.golang.com/p/YT72Eib97vB), though I'd be interested to hear 
from anyone on the list who knows of a compile-time way to prevent this 
issue.

On Thursday, August 7, 2025 at 11:46:13 AM UTC-6 John Wilkinson wrote:

> 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/28046405-7dc7-4717-be7e-fbbdc4f2ffc1n%40googlegroups.com.

Reply via email to