How can I use generic method in Go?

75 Views Asked by At

I prepared GORM methods in Database.go and I want to keep them generic. The reason I do this is to avoid duplicate codes. I have prepared frequently used operations as below.

package database

import (
    "context"
    "fmt"
    "go-microservice/internal/constants"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/schema"
    "time"
)

type DbOperations[T any] interface {
    Ready() bool
    GetAll(ctx context.Context) ([]T, error)
    GetByParam(ctx context.Context, param any) (any, error)
    Create(ctx context.Context, data any) error
    Update(ctx context.Context, data any) error
    Delete(ctx context.Context, model any, param any) error
}

type DbConnection[T any] struct {
    DB *gorm.DB
}

func NewDatabaseConnection[T any]() DbConnection[T] {
    dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s",
        constants.Host,
        constants.User,
        constants.Password,
        constants.Dbname,
        constants.Port,
        constants.SSLMode)

    db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{
        NamingStrategy: schema.NamingStrategy{
            TablePrefix: "wisdom.",
        },
        NowFunc: func() time.Time {
            return time.Now().UTC()
        },
        QueryFields: true})

    return DbConnection[T]{DB: db}
}

func (db *DbConnection[T]) Ready() bool {
    var ready string
    tx := db.DB.Raw("SELECT 1 as ready").Scan(&ready)
    if tx.Error != nil {
        return false
    }
    if ready == "1" {
        return true
    }
    return false
}

func (db *DbConnection[T]) GetAll(ctx context.Context) ([]T, error) {
    list := make([]T, 0)
    result := db.DB.WithContext(ctx).Find(&list)
    return list, result.Error
}

func (db *DbConnection[T]) GetByParam(ctx context.Context, param any) (any, error) {
    var model any
    result := db.DB.WithContext(ctx).Where(param).Find(&model)
    return model, result.Error
}

func (db *DbConnection[T]) Create(ctx context.Context, data any) error {
    result := db.DB.WithContext(ctx).Create(data)
    return result.Error
}

func (db *DbConnection[T]) Update(ctx context.Context, data any) error {
    result := db.DB.WithContext(ctx).Model(data)
    return result.Error
}

func (db *DbConnection[T]) Delete(ctx context.Context, model any, param any) error {
    result := db.DB.WithContext(ctx).Delete(model, param)
    return result.Error
}

When I want to use these methods, for example db_products, I want to pull the method and use it, but when I do this I get an error. db_products.go codes are as follows:

package database

import (
    "context"
    "go-microservice/internal/models"
)

type ProductOperations interface {
    GetAllProducts(ctx context.Context) ([]models.Products, error)
}

func (db *DbConnection[T]) GetAllProducts(ctx context.Context) ([]models.Products, error) {
    var re []models.Products
    re, _ = db.GetAll(ctx)
    return re, nil
}

But, here I am getting error from IDE. The error is as follows:

Cannot assign []T to re (type []models.Products) in multiple assignment

How do I solve this error? Thank you.

1

There are 1 best solutions below

7
nimdrak On

Summary

There is a difference between any in type parameter and any in regular function parameter. Your case is the former one for generics. So you need to instantiate T(decide T's concrete type) first when assigning it to the concrete variables

In detail

Type definition spec

If the type definition specifies type parameters, the type name denotes a generic type. Generic types must be instantiated when they are used.

Instantiations spec

A generic function or type is instantiated by substituting type arguments for the type parameters. [...]

It's because in GetAllProducts, type T is not decided yet. So it is impossible to assign []models.Products to T.

func (db *DbConnection[T]) GetAllProducts() {
    var re []models.Products
    re, _ = db.GetAll()
}

But you can assign models.Products to T if T is decided as models.Products, like this.

db := DbConnection[models.Products]{}
var re []models.Products
re, _ = db.GetAll()

I reproduce your problem in the simplified version(https://go.dev/play/p/GHkDThFLOKG). You can find the same error GetAllProducts() but in the main code, it works.

References