How does one allow a generic type that is convertible to a pointer to parameterise another generic type that is convertible to a pointer?

Issue

From the type parameters proposal, there is a useful section describing how one should define a type constraint such that a type that already implements the interface via it’s pointer receivers can still be used as a type parameter, for example:

type ExplainedExampleGeneric[T any] interface {
    GetBool() bool
    *T // non-interface type constraint element
}

type ExplainedImpl struct{ bully bool }

func (e *ExplainedImpl) GetBool() bool { return e == nil || e.bully }

func Print[T any, PT ExplainedExampleGeneric[T]](impl T) {
    fmt.Println(PT(&impl).GetBool())
}

func main() {
    Print[ExplainedImpl](ExplainedImpl{}) // Prints: false
}

For my use-case, I want to use this ExplainedExampleGeneric as a parameter to another generic type:

type UncharteredGeneric[T any, U any, V ExplainedExampleGeneric[U]] interface {
    GetString() string
    GetExplainedExampleGeneric() V
    *T // non-interface type constraint element
}

type ExplainedExampleGeneric[T any] interface {
    GetBool() bool
    *T // non-interface type constraint element
}

This compiles, however, when trying to compile a function to utilise this UncharteredGeneric type, I get the following errors:

./prog.go:35:40: got 2 arguments but 3 type parameters
./prog.go:36:17: cannot convert &impl (value of type *T) to type PT
func UncharteredPrint[T any, U any, PT UncharteredGeneric[T, U]](impl T) { -> got 2 arguments but 3 type parameters
    fmt.Println(PT(&impl).GetExplainedExampleGeneric().GetBool()) -> cannot convert &impl (value of type *T) to type PT
}

Finally, when I try to call this function, my type is unable to be inferred link to playground:

./prog.go:36:40: got 2 arguments but 3 type parameters
./prog.go:37:17: cannot convert &impl (value of type *T) to type PT
./prog.go:42:50: cannot infer PT (prog.go:36:37)
package main

import "fmt"

type UncharteredGeneric[T any, U any, V ExplainedExampleGeneric[U]] interface {
    GetString() string
    GetExplainedExampleGeneric() V
    *T // non-interface type constraint element
}

type ExplainedExampleGeneric[T any] interface {
    GetBool() bool
    *T // non-interface type constraint element
}

type UncharteredImpl struct{ some string }

func (e *UncharteredImpl) GetExplainedExampleGeneric() ExplainedImpl { return ExplainedImpl{} }
func (e *UncharteredImpl) GetString() string {
    if e == nil {
        return ""
    }
    return e.some
}

type ExplainedImpl struct{ bully bool }

func (e *ExplainedImpl) GetBool() bool { return e == nil || e.bully }

func Print[T any, PT ExplainedExampleGeneric[T]](impl T) {
    fmt.Println(PT(&impl).GetBool())
}

func UncharteredPrint[T any, U any, PT UncharteredGeneric[T, U]](impl T) { -> got 2 arguments but 3 type parameters
    fmt.Println(PT(&impl).GetExplainedExampleGeneric().GetBool()) -> cannot convert &impl (value of type *T) to type PT
}

func main() {
    Print[ExplainedImpl](ExplainedImpl{})
    UncharteredPrint[UncharteredImpl, ExplainedImpl](UncharteredImpl{}) -> cannot infer PT (prog.go:36:37)
}

Any idea what I’m doing wrong? Seems like it should be possible but I’m unsure of what I’m missing here, any help would be greatly appreciated.

Solution

First, the interface UncharteredGeneric is a parametrized type, you must explicitly supply all three type parameters.

Therefore in UncharteredPrint function type parameter list, PT should be:

PT UncharteredGeneric[T, U, V]

Now what is V? As seen in UncharteredGeneric own type param list, V must satisfy ExplainedExampleGeneric[U], hence this is how we define it. The full signature becomes:

UncharteredPrint[T any, U any, V ExplainedExampleGeneric[U], PT UncharteredGeneric[T, U, V]](impl T)

You can’t use directly ExplainedExampleGeneric[U] to instantiate UncharteredGeneric because ExplainedExampleGeneric includes the type element *T; you can only use it as a constraint for V.

Finally, after all this PT would be inferred as *UncharteredImpl, but this doesn’t yet implement UncharteredGenericV is instantiated with *ExplainedImpl, but the method is declared as GetExplainedExampleGeneric() ExplainedImpl. You have also to fix the method signature to return the pointer type:

func (e *UncharteredImpl) GetExplainedExampleGeneric() *ExplainedImpl { return &ExplainedImpl{} }

Then, it compiles and runs: https://go.dev/play/p/zCZd53ys-5J

Answered By – blackgreen

Answer Checked By – David Marino (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.