Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go with Generics: type *T is pointer to type parameter, not type parameter

Tags:

generics

go

Probably a golang beginner's question :)

I'm facing following compiler error when trying to compile the code below.

I want to implement an object store for different types (here A and B) sharing a common ID field. Following the DRY idea, I want to implement the store using generics.

When adding an object, I want to set its ID field using the GS interface (the actual code is a bit more complex of course), but the compiler does not want me to do that.

./prog.go:29:7: item.SetId undefined (type *T is pointer to type parameter, not type parameter)

./prog.go:34:24: A does not implement GS (SetId method has pointer receiver)

Is there a recommended way to solve this? Thanks in advance!!

package main

import "fmt"

type A struct {
    ID      string
    AMember string
}
type B struct {
    ID      string
    BMember string
}

type GS interface {
    Id() string
    SetId(string)
}

func (s A) Id() string      { return s.ID }
func (s *A) SetId(i string) { s.ID = i }
func (s B) Id() string      { return s.ID }
func (s *B) SetId(i string) { s.ID = i }

type MyStore[T GS] struct {
    values map[string]*T
}

func (s *MyStore[T]) add(item *T) {
    item.SetId("aa")
    s.values["aa"] = item
}

func main() {
    var storeA = &MyStore[A]{}
    storeA.values = make(map[string]*A)
    a := &A{}

    storeA.add(a)

    fmt.Println(a.Id())
}
like image 294
Martin Avatar asked Dec 01 '25 23:12

Martin


1 Answers

About using *T

In short, a type parameter is not its constraint. The constraint only determines what operations are available on T, it doesn't imply anything about *T, which is now just an unnamed pointer type. This is the meaning of:

type *T is pointer to type parameter, not type parameter

As a consequence, as in your case, the method set of *T does not automatically include pointer receiver methods declared on the T's concrete type A, and it does not implement interfaces that would be implemented by *A.

You'd have to make that explicit to the compiler by setting additional constraints. In a simplified form it would be something like:

func Foo[T any, PT interface { SetId(string); *T}](v T) {}

You can find more extensive examples and variations on this use case here:

  • Go 1.18 Generics how to define a new-able type parameter with interface
  • How can I instantiate a new pointer of type argument with generic Go?
  • What is the generic type for a pointer that implements an interface?

About implementing constraints

The reason this instantiation &MyStore[A]{} fails is clearly reported by the error message:

A does not implement GS (SetId method has pointer receiver)

In other words SetId() is declared on *A, and not A. Therefore you should instantiate MyStore with *A:

var storeA = &MyStore[*A]{}

Then change the occurrences of *T in the struct/method definition to T:

type MyStore[T GS] struct {
    values map[string]T // just T instead of *T
}

func (s *MyStore[T]) add(item T) {
}

Upon instantiation with *A the type of the field would become equivalent to map[string]*A thus making the assignment storeA.values = make(map[string]*A) valid, and the method signature to add(item *A) thus allowing storeA.add(&A{}).

Fixed playground: https://gotipplay.golang.org/p/dcUVJ5YQK_b

like image 167
blackgreen Avatar answered Dec 04 '25 19:12

blackgreen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!