Inheritance Semantics in Go

Contemporary application design discipline is deeply rooted in Object Oriented Analysis and Design and inheritance is a key concept in OOAD. Go does not support classes and inheritance in their classic OOP sense but since many of us are trained in OOP, the loss of an important design concept sometimes feels restrictive.

Even though I knew about embedding and interfaces, their connection with classic inheritance wasn’t quite obvious. I set out to understand how I could emulate the coarse inheritance semantics in Go, without going into fine nuances. That in turn has helped me understand embedding and interfaces in a deeper way and I hope it would help me better design Go types and methods for extensibility.

The Task

For the purpose of this exercise, we shall attempt to emulate the following class hierarchy:

Class Diagram
Deriving from class “Hello”

The class Hello has two public methods along with its constructor. genHello() is used to generate a greeting message, like “Hello World!” while sayHello() actually prints it out. We shall derive AltHello class from Hello, which stores its own copy of the greeting and overrides the genHello() method to give us a different message.

Here’s an implementation of the above scheme in C++:

#include
#include
#include

using namespace std;

class Hello
{

private:
	string greeting;

public:
	Hello(string greeting) : greeting(greeting)
	{	}

	virtual string genHello(string &to)
	{
		ostringstream ss;
		ss << this->greeting << " " << to << "!";
		return ss.str();
	}

	virtual void sayHello(string &to)
	{
		cout << this->genHello(to) << endl;
	}

};

class AltHello : public Hello
{
 private:
	string greeting;

 public:
	AltHello(string greeting) : greeting(greeting + " `"), Hello("")
	{}

	string genHello(string &to)
	{
		ostringstream ss;
		ss << this->greeting << "Alt' " << to << "!"; return ss.str(); } }; int main(void) { string greeting = string("Hello"); string to = string("World"); Hello *h = new Hello(greeting); h->sayHello(to); // Hello World!

	Hello *ah = new AltHello(greeting);
	ah->sayHello(to); // Hello `Alt' World!

	delete(h);
	delete(ah);
}

The output from this program is:

Hello World!
Hello `Alt' World!

Type Embedding and Implementation Inheritance

To inherit the implementation from another type in Go, we use embedding. So let’s try it out. Here is the first draft of our port of the above C++ code into Go.

package main

import (
	"fmt"
)

type Hello struct {
	greeting string
}

func (h Hello) SayHello(to string) {
	fmt.Println(h.GenHello(to))
}

func (h Hello) GenHello(to string) string {
	return h.greeting + " " + to + "!"
}

type AltHello struct {
	greeting string
	Hello
}

func (a AltHello) GenHello(to string) string {
	return a.greeting + "Alt' " + to + "!"
}

func main() {
	h := Hello{"Hello"}
	h.SayHello("World") // Hello World!

	ah := AltHello{"Hello `", Hello{""}}
	ah.SayHello("World") //  World!
}

When this program is executed, we get:

Hello World!
 World!

Wait, this doesn’t match the C++ output. What are we missing? While AltHello inherited the implementation of SayHello() from Hello, the version of GenHello() that gets called is also from Hello. That neither picks up the data from AltHello.greeting, nor the altered formatting from AltHello.GenHello(). How do we make it such that we can call the derived version of GenHello() without having to re-implement SayHello()? The first step to achieving that is to be able to access both instances through a common type, like we do with Hello* in C++.

Type Substitution Through Interfaces

Go accomplishes type substitution through the use of Interfaces. Interfaces allow us to define an abstract behaviour and have different types satisfy that behaviour. Since that’s the relationship we want to establish between our two types, let’s try creating an interface for the methods of our types and see how it goes.

package main

import (
	"fmt"
)

type Greeter interface {
	SayHello(string)
	GenHello(string) string
}

type Hello struct {
	greeting string
}

func (h Hello) SayHello(to string) {
	fmt.Println(h.GenHello(to))
}

func (h Hello) GenHello(to string) string {
	return h.greeting + " " + to + "!"
}

func NewHello(msg string) Hello {
	return Hello{msg}
}

type AltHello struct {
	greeting string
	Hello
}

func (a AltHello) GenHello(to string) string {
	return a.greeting + "Alt' " + to + "!"
}

func NewAltHello(msg string) AltHello {
	return AltHello{msg + " `", NewHello("")}
}

func main() {
	var g, ag Greeter
	g = NewHello("Hello")
	g.SayHello("World") // Hello World!

	ag = NewAltHello("Hello")
	ag.SayHello("World") //  World!
}

The output from this is:

Hello World!
 World!

The revised code has been highlighted in bold. Although, this has allowed us to unify Hello and AltHello under a common type, Greeter, it still hasn’t allowed us to achieve dynamic dispatch of AltHello.GenHello() through the interface. This is the part where I stumbled a bit because the expectation from classic OOP languages is that the above changes should be sufficient to enable dynamic dispatch.

What is actually happening here is that even though we can reach Hello.SayHello() from an AltHello instance, whether we go through an interface or not, Hello.SayHello() does not have the information needed to switch to AltHello.GenHello() where needed. To understand why, let’s look at the method implementation in isolation:

func (h Hello) SayHello(to string) {
	fmt.Println(h.GenHello(to))
}

The parts in bold should highlight the problem more clearly. The receiver of SayHello() method is defined as an instance of type Hello. That being the case, SayHello() will always call Hello.GenHello(). How do we make SayHello() call GenHello() on the correct receiver?

Enabling Dynamic Dispatch

The key to this lies in how dynamic dispatch works in, e.g., C++. The class methods implicitly receive a pointer to the object, named this. Even if the pointer is of a base class type, it points to an object of the class that we instantiated, whether it is the base class or the derived class. That in turn holds a reference to its own set of methods. The same effect can be achieved in Go if we somehow emulate the this pointer. How do we do that? Like so:

type Greeter interface {
	SayHello(Greeter, string)
	GenHello(string) string
}

func (h Hello) SayHello(g Greeter, to string) {
	fmt.Println(g.GenHello(to))
}

From the previous section, we had understood that type substitution can be achieved through interfaces. To solve our problem of making SayHello() call GenHello() on the right type, we pass it an instance of the concerned type through a Greeter interface parameter and make it call GenHello() on the interface parameter. Voila! Now our method can automatically call the correct instance of the overridden function and we have Dynamic Dispatch!

Here’s the revised code in full:

package main

import (
	"fmt"
)

type Greeter interface {
	SayHello(Greeter, string)
	GenHello(string) string
}

type Hello struct {
	greeting string
}

func (h Hello) SayHello(g Greeter, to string) {
	fmt.Println(g.GenHello(to))
}

func (h Hello) GenHello(to string) string {
	return h.greeting + " " + to + "!"
}

func NewHello(msg string) Hello {
	return Hello{msg}
}

type AltHello struct {
	greeting string
	Hello
}

func (a AltHello) GenHello(to string) string {
	return a.greeting + "Alt' " + to + "!"
}

func NewAltHello(msg string) AltHello {
	return AltHello{msg + " `", NewHello("")}
}

func main() {
	var g, ag Greeter
	g = NewHello("Hello")
	g.SayHello(g, "World") // Hello World!

	ag = NewAltHello("Hello")
	ag.SayHello(ag, "World") // Hello `Alt' World!
}

And here’s the output:

Hello World!
Hello `Alt' World!

That’s exactly what we wanted!

Conclusion

There are three semantic aspects to classic OOP inheritance – inheriting implementation, type substitution and dynamic dispatch of overridden methods. We have seen how we can use type embedding to inherit the implementation, interfaces for type substitution and passing an interface parameter to a method to enable dynamic dispatch.

Although Go does not support classes and inheritance in the classic OOP sense, understanding how to achieve the effects of inheritance in part or whole would help in designing applications meant to be written in Go. Go separates implementation inheritance from type substitution and makes dynamic dispatch more explicit and deliberate. This could potentially help in coming up with cleaner design or avoiding mistakes in design modification since the decision to use one of these features does not implicitly enable the others.

One thought on “Inheritance Semantics in Go

Comments are closed.