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.