I'm confused by generics. I've read a bunch of answers to similar questions and I'm still stumped.
I get that the answer here involves telling the compiler that T and *T are related. That while the implicit type conversion from one to the other happens normally in straight code, that doesn't happen in generics without more code. Could someone please walk me through this?
package main
type Mungeable interface {
Munge() // a Mungeable is a thing that can be Munge'd
}
type Box[T Mungeable] struct {
Contents *T // A box is a type based on a Mungeable
}
type Foo struct{} // Foo is a struct...
func (f *Foo) Munge() {} // ...that can be munged.
func (b *Box[Foo]) Print() {
_ = b.Contents.Munge()
}
produces:
./prog.go:16:17: b.Contents.Munge undefined (type *Foo is pointer to type parameter, not type parameter)
Go build failed.
what do I need to change to get this to compile?
playground here: https://go.dev/play/p/gNqxWhlmWmz
CodePudding user response:
Mungable
is an interface, a pointer to an interface has no method-set (unlike a pointer to concrete type, which has a method-set containing the methods of its base type in addition to whatever methods were declared for the pointer type), so you need to dereference the pointer before calling the interface's method.
Another issue is that Munge
does not have a return value, therefore the assignment statement _ = ...Munge()
will cause a compile time error.
The following works:
package main
type Mungeable interface {
Munge() // a Mungeable is a thing that can be Munge'd
}
type Box[T Mungeable] struct {
Contents *T // A box is a type based on a Mungeable
}
type Foo struct{} // Foo is a struct...
func (f *Foo) Munge() {} // ...that can be munged.
func (b *Box[Foo]) Print() {
(*b.Contents).Munge()
}
func main() {}
https://go.dev/play/p/HWW6k47Cdvh
CodePudding user response:
Your understanding of methods and interfaces is wrong. The method set of Foo
includes those that defined with the receiver of type Foo
, while the method set of *Foo
includes methods defined with the receiver of both types Foo
and *Foo
. Given these declarations
type Mungeable interface {
Munge()
}
type Foo struct {}
func (f *Foo) Munge() {}
Then Foo
does not implement Mungeable
, it is *Foo
that does so. This is reflected in the fact that you cannot assign a value of type Foo
to a variable of type Mungeable
, and var x Mungeable = new(Foo)
is fine. On the contrary, if your Munge
method is defined as so
func (f Foo) Munge() {}
Then both Foo
and *Foo
would implement Mungeable
.
The second mistake of yours is that func (f *Box[Foo]) Print()
does not define a method with the receiver of type Box[Foo]
, it defines a method with the receiver of generics type Box
with the type parameter declared as Foo
. In other words, it is the same as func (f *Box[T]) Print()
. If you want to act on Box[Foo]
only, then I think you must put it in the parameter list, as I don't think Go allows specialisation of generics classes.
The solution to your problem is that you can just make Contents
of type T
and change type parameter to *Foo
, like this:
package main
type Mungeable interface {
Munge() // a Mungeable is a thing that can be Munge'd
}
type Box[T Mungeable] struct {
Contents T // A box is a type based on a Mungeable
}
type Foo struct{} // Foo is a struct...
func (f *Foo) Munge() {} // ...that can be munged.
func Print(b *Box[*Foo]) {
b.Contents.Munge()
}