Home > Blockchain >  Cannot use variable of type *T as type in argument
Cannot use variable of type *T as type in argument

Time:04-08

I'm learning Go 1.18 generics and I'm trying to understand why I'm having trouble here. Long story short, I'm trying to Unmarshal a protobuf and I want the parameter type in blah to "just work". I've simplified the problem as best I could, and this particular code is reproducing the same error message I'm seeing:

./prog.go:31:5: cannot use t (variable of type *T) as type stringer in argument to do:
    *T does not implement stringer (type *T is pointer to type parameter, not type parameter)
package main

import "fmt"

type stringer interface {
    a() string
}

type foo struct{}

func (f *foo) a() string {
    return "foo"
}

type bar struct{}

func (b *bar) a() string {
    return "bar"
}

type FooBar interface {
    foo | bar
}

func do(s stringer) {
    fmt.Println(s.a())
}

func blah[T FooBar]() {
    t := &T{}
    do(t)
}

func main() {
    blah[foo]()
}

I realize that I can completely simplify this example by not using generics (i.e., pass the instance to blah(s stringer) {do(s)}. However, I do want to understand why the error is happening.

What do I need to change with this code so that I can create an instance of T and pass that pointer to a function expecting a particular method signature?

CodePudding user response:

In your code there's no relationship between the constraints FooBar and stringer. Furthermore the methods are implemented on the pointer receivers.

A quick and dirty fix for your contrived program is to get rid of FooBar and constrain stringer to pointers:

type stringer[T any] interface {
    *T
    a() string
}

But let's say your program is actually more complex than that, another solution that somewhat preserves your programs' semantics would be this:

type FooBar[T foo | bar] interface {
    *T
    stringer
}

func blah[T foo | bar, U FooBar[T]]() {
    var t T
    do(U(&t))
}

So what's going on here?

First, the relationship between a type parameter and its constraint is not identity: T is not FooBar. You cannot use T like it was FooBar, therefore *T is definitely not equivalent to *foo or *bar.

So when you call do(t), you're attempting to pass a type *T into something that expects a stringer, but T, pointer or not, just does not inherently have the a() string method in its type set.

Step 1: add the method a() string into the FooBar interface (by embedding stringer):

type FooBar interface {
    foo | bar
    stringer
}

But that's not enough yet, because now none of your types actually implement it. Both declare the method on the pointer receiver.

Step 2: change the types in the union to be pointers:

type FooBar interface {
    *foo | *bar
    stringer
}

This constraint now works, but you have another problem. You can't declare composite literals when the constraint doesn't have a core type. So t := T{} is also invalid. We change it to:

func blah[T FooBar]() {
    var t T // already pointer type
    do(t)
}

Now this compiles, but t is actually the zero value of a pointer type, so it's nil. Your program doesn't crash because the methods just return some string literal.

If you need to also initialize the memory referenced by the pointers, inside blah you need to know about the base types.

Step 3: So you add T foo | bar as one type param, and change the signature to:

func blah[T foo | bar, U FooBar]() {
    var t T
    do(U(&t))
}

Done? Not yet. The conversion U(&t) is still invalid because the type set of both U and T don't match. You need to now parametrize FooBar in T.

Step 4: basically you extract FooBar's union into a type param, so that at compile time its type set will include only one of the two types:

type FooBar[T foo | bar] interface {
    *T
    stringer
}

The constraint now can be instantiated with T foo | bar, preserve type safety, pointer semantics and initialize T to non-nil.

func (f *foo) a() string {
    fmt.Println("foo nil:", f == nil)
    return "foo"
}

func main() {
    blah[foo]()
}

Prints:

foo nil: false
foo

Playground: https://go.dev/play/p/src2sDSwe5H


If you can instantiate blah with pointer types, or even better pass arguments to it, you can remove all the intermediate trickery:

type FooBar interface {
    *foo | *bar
    stringer
}

func blah[T FooBar](t T) {
    do(t)
}

func main() {
    blah(&foo{})
}

CodePudding user response:

https://golang.google.cn/blog/intro-generics

Until recently, the Go spec said that an interface defines a method set, which is roughly the set of methods enumerated in the interface. Any type that implements all those methods implements that interface.

package main

import "fmt"

type stringer interface {
    a() string
}

type foo struct{}

func (f foo) a() string {
    return "foo"
}

type bar struct{}

func (b bar) a() string {
    return "bar"
}

type FooBar interface {
    stringer
}

func do(s stringer) {
    fmt.Println(s.a())
}

func blah[T FooBar]() {
    t := new(T)
    do(*t)
}

func main() {
    blah[foo]()
}

and you can delete type FooBar, because FooBar(Generics) is same as stringer(Interface)

  • Related