Structs

So far all of the examples in this section have been focused on functions, but struct types can utilize generics as well. For example, the function PrintIDAndSum[T ~string, K Numeric](T, func(...K) K) can be decomposed into a struct that:

  • has fields for the:
    • ID
    • numeric values
    • sum function
  • has a function for printing the ID and sum

Okay, let’s start by defining a struct that as a field named ID which can be a string value or a type definition with an underlying type of string:

  1. // Ledger is an identifiable, financial record.
  2. type Ledger[T ~string] struct {
  3. // ID identifies the ledger.
  4. ID T
  5. }

The example demonstrates that defining structs with generic fields uses the same syntax in between the brackets as when defining generic functions. Let’s go ahead and add the other fields:

  1. // Ledger is an identifiable, financial record.
  2. type Ledger[T ~string, K Numeric] struct {
  3. // ID identifies the ledger.
  4. ID T
  5. // Amounts is a list of monies associated with this ledger.
  6. Amounts []K
  7. // SumFn is a function that can be used to sum the amounts
  8. // in this ledger.
  9. SumFn SumFn[K]
  10. }

So far I think this should make sense as it does not differ too much from what we have already covered. However, the next step is new — defining a function on a generic struct. One of the use cases is:

has a function for printing the ID and sum

Normally a function would be defined on Ledger[T, K] like so:

  1. func (l Ledger) PrintIDAndSum() {}

However, because Ledger has generic types, their symbols must be included in the function receiver:

  1. // PrintIDAndSum emits the ID of the ledger and a sum of its amounts on a
  2. // single line to stdout.
  3. func (l Ledger[T, K]) PrintIDAndSum() {
  4. fmt.Printf("%s has a sum of %v\n", l.ID, l.SumFn(l.Amounts...))
  5. }

Notice the constraints do not need to be included, only the symbols for the constraints. With all of this in place, the same example from the previous page can be rewritten using the new type Ledger[T, K] (Golang playground):

  1. func main() {
  2. Ledger[string, int]{
  3. ID: "acct-1",
  4. Amounts: []int{1, 2, 3},
  5. SumFn: Sum[int],
  6. }.PrintIDAndSum()
  7. }

The above program produces the expected output:

  1. acct-1 has a sum of 6

Please note that all of the types must be supplied explicitly. This is because type inference is a convenience feature for functions and does not apply to structs.


Next: Structural constraints