44

What is the equivalent of this C# code in Go, how can I build it

    class ModelX<T>
    {
        public T Data { get; set; }
    }

    ModelX<int>

I have tried something like:

    type ModelX<T> struct {
        ModelY
        Data []T
    }

    m := ModelX<T>

How to do this? Is that possible?

0

1 Answer 1

98

Starting with Go 1.18, you can define generic types:

type Model[T any] struct {
    Data []T
}

A generic type must be instantiated1 when used, and instantiation requires a type parameter list:

func main() {
    // passing int as type parameter
    modelInt := Model[int]{Data: []int{1, 2, 3}}
    fmt.Println(modelInt.Data) // [1 2 3]

    // passing string as type parameter
    modelStr := Model[string]{Data: []string{"a", "b", "c"}}
    fmt.Println(modelStr.Data) // [a b c]
}

More info and common gotchas about instantiations: Go error: cannot use generic type without instantiation

If you declare methods on a generic type, you must repeat the type parameter declaration on the receiver, even if the type parameters are not used in the method scope — in which case you may use the blank identifier _ to make it obvious:

func (m *Model[T]) Push(item T) {
    m.Data = append(m.Data, item)
}

// not using the type param in this method
func (m *Model[_]) String() string {
    return fmt.Sprint(m.Data)
}

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

About type inference

An important detail is that — unlike functions2 —, generic types must always supply all3 type parameters upon instantiation. For example, this type:

type Foo[T any, P *T] struct {
    val T
    ptr P
}

must be instantiated with both types, even if some of them could be inferred:

func main() {
    v := int64(20)
    foo := Foo[int64, *int64]{val:v, ptr: &v}

    fmt.Println(foo)
}

A workaround to avoid instantiating the struct at each point of use is to use a constructor function (note that this works for any defined type, not just structs). For example:

func newFoo[T any](v T) Foo[T, *T] {
    return Foo[T, *T]{val:v, ptr: &v}
}
 
func main() {
    v := int64(20)
    foo := newFoo(v) // here type arguments are inferred

    fmt.Println(foo)
}

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


Footnotes:

1: Language specs about instantiations: https://golang.org/ref/spec#Instantiations

2: The quote from the specs is "Calls to parameterized functions may provide a (possibly partial) type argument list, or may omit it entirely if the omitted type arguments are inferrable from the ordinary (non-type) function arguments.". This quote excludes parametrized types

3: in early beta releases, the type param list in generic types could be partial; this feature has been disabled.

Sign up to request clarification or add additional context in comments.

1 Comment

Adding a func NewModel[T any](data []T) Model[T] { return Model[T]{data} } can DRY some use cases a bit by leveraging type inference; you then can do, e.g., modelInt := NewModel([]int{1, 2, 3}) without repeating the int. See go.dev/play/p/t-brVDwpQ-V

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.