Trying to understand generics with use of structs and interfaces

Hello!

I’m trying to learn generics and trying to put it all together, and I can’t seem to understand it. Can I use the union type and get access to the properties inside the struct from an interface?

Thank you!


type rectangle struct {
	length, height float64
}

type square struct {
	length, height float64
}

type Shape interface {
	rectangle | square
}

func getArea[T Shape](shape T) float64 {
	return shape.length * shape.height
}

func main() {
	rect := rectangle{1.3, 4.3}
	sqr := square{3.2, 3.4}

	fmt.Println(getArea(rect))
	getArea(sqr)
}

Compiler errors:

# command-line-arguments
./main.go:83:15: shape.length undefined (type T has no field or method length)
./main.go:83:30: shape.height undefined (type T has no field or method height)

Hi, Tauqueer, and welcome to to the forum!

Interfaces in Go are just method sets; struct fields are not part of the interface, so you cannot write a generic getArea function that way.

In your case, I don’t actually see a use case for generics: Just interfaces. Shape can be redefined as an interface with Length and Height methods and getArea can use those: Go Playground - The Go Programming Language

As for trying to learn them, I’m not sure I have any helpful guidelines. I think I have two places where I wrote code that uses generics in my own code:

  1. workers/workers.go at 0af79d6a76499b1b0cbd9521b1cb55977f0fe4bd · skillian/workers · GitHub
  2. expr/stream.go at da8a0af9071142cdbb3196a74e36de06333fd16a · skillian/expr · GitHub

The first example might be useful to you to see how it works: The Work function receives two main parameters:

  1. A channel of some generic type (probably a struct of parameters, maybe simpler like a string, etc.),
  2. A worker function that processes an element from the channel and returns a result.

Given those two things, the Work function returns a channel of results whose type is determined by the worker function.

I notice I don’t have any tests to demonstrate how it works… :sweat_smile: :grin:

The Each function is more specific to another project I’m working on, so it’s probably less useful to you than the workers example. I only included it to demonstrate that, at least for me, generics are not super useful.

Hi Sean,

Thank you so much for the reply. That Go playground is super helpful! I come from the Typescript land, so definitely different. So my initial thought was, what if I had a Shape, like circle, square, rectangle, etc., and then they would have some sort of filtering? This is obviously an arbitrary example, but I was trying to understand what’s possible with interfaces and structs.

So my original example was something like this.

type rectangle struct {
	length, height float64
}

type square struct {
	length, height float64
}

type circle struct {
	radius float64
}

type Shape interface {
	rectangle | square | circle
}

func isCircle[T Shape](x T) bool {
	_, ok := interface{}(x).(circle)
	return ok
}

func getArea[T Shape](shape T) float64 {
	if isCircle(shape) {
		return 2 * math.Pi * shape.radius
	}
	return shape.length * shape.height
}

func main() {
	rect := rectangle{1.3, 4.3}
	sqr := square{3.2, 3.4}
	circ := circle{3.2}

	getArea(rect)
	getArea(sqr)
	getArea(circ)
}

Though I like how you’ve implemented it, and my example introduces some code smell :sweat_smile:
It does feel more verbose but the codes are a lot cleaner that way.

Thanks again!

If this needs to work for non-rectangles, too, I think getArea should no longer be a standalone function and should instead be on the Shape interface. Otherwise, you will need to change getArea’s implementation whenever you need to add a new shape (e.g. ellipses, or other polygons, or “free-form” shapes with or without curves, etc.). If you move getArea into an Area function on Shape, not only do you no longer need getArea, but isCircle, as well: Go Playground - The Go Programming Language

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.