Go: embedded type != inheritance

I changed the stack recently. I switched from .NET to Golang. Leraning new language and the stack brings many gotchas. I’ll write about them as I face them on my journey, especially like OOP guy coming from .NET

When designing a library I use to define a role interface representing a dependency. I’d like to separate it due to several reasons: separation of concerns, shaping the design of structs and simplifying the testing.

When putting it together, I wrote a class (in Golang in the form of a struct type) and defined the “receiver” style functions conforming interface API, example:

type Shape interface {
Area() int
}
type Square struct {
a int
}
func NewSquare(a int) *Square {
return &Square{a: a}
}
func (s *Square) Area() int {
return s.a * s.a
}
func main() {
var s Shape = NewSquare(5)
fmt.Printf("Square's area is %v\n", s.Area())
}

That’s all. Golang compiler verifies that my functions meets behaviour the interface defines, enforced by line 18.

Otherwise the error would be:

//./shape.go:83:6: cannot use NewSquare(5) (type *Square) as type Shape in assignment:
// *Square does not implement Shape (missing Area method)

I’d like to be more explicit in a style of OOP so that I define a class and express that the class implements the interface.

Golang supports the concept called “embedded types”. I decided to improve my design and use it.

package main
import (
"fmt"
)
type Shape interface {
Area() int
}
type Square struct {
Shape
a int
}
func NewSquare(a int) *Square {
return &Square{a: a}
}
func main() {
s := NewSquare(5)
fmt.Printf("Square's area is %v\n", s.Area())
}
//result
//panic: runtime error: invalid memory address or nil pointer dereference
// [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x109e933]

When using embedded types, the Go compiler promotes all functions defined on the embedded interface, in this case Shape to the received type Square. So in our case, the function Area() int defined on the interface Shape is propagated to Square without any explicit declaration.

The compilation succeeds. This means that it is possible to call square.Area() method. But it results in the runtime error. Great! In order to fix it I still need to define all the receiver-style methods as before and I’m done. Simple? Clean? No so much. Actually it gets more confusing.

Reason: it is also possible to call square.Shape.Area().

type Shape interface {
Area() int
}
type Square struct {
Shape
a int
}
func NewSquare(a int) *Square {
return &Square{a: a}
}
func (s *Square) Area() int {
return s.a * s.a
}
func main() {
s := NewSquare(5)
fmt.Printf("Square's area is %v\n", s.Shape.Area())
}
// output
//panic: runtime error: invalid memory address or nil pointer dereference
//[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x109e933]

The result is “panic”. What’s more confusing, I could do the following:

type Rectangle struct {
a int
b int
}
func NewRectangle(a, b int) *Rectangle {
return &Rectangle{a: a, b: b}
}
func (s *Rectangle) Area() int {
return s.a * s.b
}
func main() {
s := NewSquare(5)
s.Shape = NewRectangle(10, 40)
fmt.Printf("Square's area is %v\n", s.Area())
fmt.Printf("Square's shape's area is %v\n", s.Shape.Area())
}
//output
//Square's area is 25
//Square's shape's area is 400

The end result is that I misused the embedded types towards the inheritance. They are quite different.

TLDR: using the embedded type is actually a composition with a sugar of promoting the behaviour.

In addition the promoting more embedded types with the same method signature results in the compiler exception.

type Square struct {
Shape
Rectangle
a int
}
func main() {
s := NewSquare(5)
s.Shape = NewRectangle(10, 40)
fmt.Printf("Square's area is %v\n", s.Area())
}
//output
//# command-line-arguments
//./shape.go:79:5: Square.Area is ambiguous
//./shape.go:79:5: cannot use new(Square) (type *Square) as type Shape in assignment:
// *Square does not implement Shape (missing Area method)
//./shape.go:84:39: ambiguous selector s.Area

I definitely misused the embedded types and misunderstood them. Actually, I found out several forums discussing same confusion.

I’m still looking for the usecases where using the embedded types is beneficial, for example:

  • mocking, e.g. using testify but that’s for next post.
  • composition of the inner types

Back to the inheritance

So I returned back to the basics. In order to express that the struct implements the interface I just do the following:

type Shape interface {
Area() int
}
type Square struct {
a int
}
func NewSquare(a int) *Square {
return &Square{a: a}
}
func (s *Square) Area() int {
return s.a * s.a
}
var _ Shape = new(Square)
view raw proper_usage.go hosted with ❤ by GitHub

See the line #17. This enforces compiler to check that the square implements Shape interface.

That’s all for now folks!