Home > Back-end >  Unable to assign to generic struct field
Unable to assign to generic struct field

Time:09-13

Given a generic struct:

type R2[IDTYPE comparable] struct {
    ID        IDTYPE
    IsActive  bool
}

Implementing an interface:

type Storable interface {
    Store(ctx context.Context) error
}

I would expect the following definition to work:

func (r R2[int]) Store(ctx context.Context) error {
    r.ID = 123 // not allowed
    // ...
    return nil
}

However, the method definition is not allowed. The error is:

'123' (type untyped int) cannot be represented by the type IDTYPE (int)

Is it not yet possible to do this kind of generic field assignment in Go?

Addendum: On go playground the error is:

cannot use 123 (untyped int constant) as int value in assignment

And converting to int(123) does not work. The error in this case is:

cannot use comparable(123) (untyped int constant 123) as int value in assignment

CodePudding user response:

Instantiation must happen at the type level, not on a method level, and methods can't introduce new type parameters, see How to create generic method in Go? (method must have no type parameters)

This means when you want to use R2, you then have to choose type arguments for the type parameters, and the methods can't change those, you're "stuck" with the types you choose on R2's instantiation.

Also note that since the constraint for IDTYPE is comparable, which may be string for example, the integer 123 cannot be assigned to the ID field in all cases because it may have a type of string.

If you want / must handle multiple concrete types for the IDs, generics is not the right choice. Interfaces may be used instead:

type R2 struct {
    ID       any
    IsActive bool
}

Also note that the receiver must be a pointer if you wish to modify the receiver (e.g. fields of a struct).

If you wish to restrict the values stored in ID to comparable, use a (generic) function for it.

Here's how you can do it:

type R2 struct {
    ID       any
    IsActive bool
}

func (r *R2) Store(ctx context.Context) error {
    setID(r, 123)
    return nil
}

func setID[ID comparable](r *R2, id ID) {
    r.ID = id
}

Testing it:

r := &R2{}
var s Storable = r

s.Store(context.TODO())

fmt.Println(r)

Which outputs (try it on the Go Playground):

&{123 false}

This provides flexibility (you can set any comparable values to the ID field using setID()), and provides compile-time safety: attempting to set an incomparable value will result in a compile-time error such as this:

setID(r, []int{1}) // Error: []int does not implement comparable
  • Related