Golang and inheritance

Although Golang does not support inheritance, we can achieve similar results through the usage of type definitions and type embedding.

Let's start with a simple object: we need to store contacts data:

  • name string
  • surname string
  • birthday struct

and birthday will be composed by:

  • day int16
  • month int16
  • year int 16

Our struct will be defined as below:

type birthday struct {
    day   int16
    month int16
    year  int16
}

type contact struct {
    name     string
    surname  string
    birthday birthday
}

func (c *contact) FullName() string {
    return fmt.Sprintf("%s %s", c.name, c.surname)
}

Now we want to define a new struct for European contacts that will inherit from contact and will add a Birthday method returning a string in the format 'dd/mm/yyyy'.

We could try the following approach:

package main

import "fmt"

type birthday struct {
    day   int16
    month int16
    year  int16
}

type contact struct {
    name     string
    surname  string
    birthday birthday
}

func (c *contact) FullName() string {
    return fmt.Sprintf("%s %s", c.name, c.surname)
}

// *** HERE WE DECLARE THE NEW EUContact
type EUContact = contact

func (euc *EUContact) Birthday() string {
    return fmt.Sprintf("%d/%d/%d", euc.birthday.day, euc.birthday.month, euc.birthday.year)
}

func main() {
    euc := EUContact{
        name:    "Massimiliano",
        surname: "Ziccardi",
        birthday: birthday{
            day:   14,
            month: 6,
            year:  1976,
        },
    }
    fmt.Println(euc.FullName())
    fmt.Println(euc.Birthday())
}

--- OUTPUT ---
Massimiliano Ziccardi
14/6/1976

Great! It worked. Perfect. Now we need to add a new USContact type with a Birthday method returning a string in the format 'mm/dd/yyyy'. Let's try with the same approach as before:

package main

import "fmt"

type birthday struct {
    day   int16
    month int16
    year  int16
}

type contact struct {
    name     string
    surname  string
    birthday birthday
}

func (c *contact) FullName() string {
    return fmt.Sprintf("%s %s", c.name, c.surname)
}

type EUContact = contact

func (euc *EUContact) Birthday() string {
    return fmt.Sprintf("%d/%d/%d", euc.birthday.day, euc.birthday.month, euc.birthday.year)
}

type USContact = contact

func (usc *USContact) Birthday() string {
    return fmt.Sprintf("%d/%d/%d", usc.birthday.month, usc.birthday.day, usc.birthday.year)
}

func main() {
    euc := EUContact{
        name:    "Massimiliano",
        surname: "Ziccardi",
        birthday: birthday{
            day:   14,
            month: 6,
            year:  1976,
        },
    }
    usc := USContact{
        name:    "John",
        surname: "Doe",
        birthday: birthday{
            day:   16,
            month: 9,
            year:  1981,
        },
    }

    fmt.Println(euc.FullName())
    fmt.Println(euc.Birthday())

    fmt.Println(usc.FullName())
    fmt.Println(usc.Birthday())
}

This time something went wrong. The compiler returned an error:

./prog.go:29:23: contact.Birthday redeclared in this block
    ./prog.go:23:23: other declaration of Birthday

Why? The key is that with

type EUContact = contact

we declared just an alias for contact, not a new type. So, when we declared USContact it was just another alias and when we tried to declare another Birthday method we got the duplicate method error.

How do we solve this issue? The key is the = sign in the statement: if we remove it, our type will become a new type instead than being just an alias.

Let's remove it and see what happens

package main

import "fmt"

type birthday struct {
    day   int16
    month int16
    year  int16
}

type contact struct {
    name     string
    surname  string
    birthday birthday
}

func (c *contact) FullName() string {
    return fmt.Sprintf("%s %s", c.name, c.surname)
}

type EUContact contact

func (euc *EUContact) Birthday() string {
    return fmt.Sprintf("%d/%d/%d", euc.birthday.day, euc.birthday.month, euc.birthday.year)
}

type USContact contact

func (usc *USContact) Birthday() string {
    return fmt.Sprintf("%d/%d/%d", usc.birthday.month, usc.birthday.day, usc.birthday.year)
}

func main() {
    euc := EUContact{
        name:    "Massimiliano",
        surname: "Ziccardi",
        birthday: birthday{
            day:   14,
            month: 6,
            year:  1976,
        },
    }
    usc := USContact{
        name:    "John",
        surname: "Doe",
        birthday: birthday{
            day:   16,
            month: 9,
            year:  1981,
        },
    }

    fmt.Println(euc.FullName())
    fmt.Println(euc.Birthday())

    fmt.Println(usc.FullName())
    fmt.Println(usc.Birthday())
}

If you try to compile it, you will get another error:

./prog.go:53:18: euc.FullName undefined (type EUContact has no field or method FullName)
./prog.go:56:18: usc.FullName undefined (type USContact has no field or method FullName)

Why does that happen? Before reading on, try to answer.

Let's recap:

  1. We created a contact struct
  2. We created 2 other objects with type EUContact contact and type USContact contact
  3. We get compilation errors saying that EUContact and USContact have no FullName method

If you look at the declaration for the FullName method, its receiver is *contact, but:

  1. we are trying to call it on EUContact and USContact
  2. Golang doesn't support inheritance as we are used to seeing in object-oriented languages

Explanation of the error should be clear now: the method is not defined for EUContact or USContact. It is defined only for the contact object

Does that mean that you have to duplicate FullName for both EUContact and USContact?

While that would work, it wouldn't be a good practice. Luckily enough, Golang provides us with the ability to embed other structs to achieve what we are trying to do. To embed a struct into another struct, simply put the struct name (in our case contact) into your struct as you would do for a property, but without a name:

package main

import "fmt"

type birthday struct {
    day   int16
    month int16
    year  int16
}

type contact struct {
    name     string
    surname  string
    birthday birthday
}

func (c *contact) FullName() string {
    return fmt.Sprintf("%s %s", c.name, c.surname)
}

type EUContact struct {
    contact
}

func (euc *EUContact) Birthday() string {
    return fmt.Sprintf("%d/%d/%d", euc.birthday.day, euc.birthday.month, euc.birthday.year)
}

type USContact struct {
    contact
}

func (usc *USContact) Birthday() string {
    return fmt.Sprintf("%d/%d/%d", usc.birthday.month, usc.birthday.day, usc.birthday.year)
}

func main() {
    euc := EUContact{
        name:    "Massimiliano",
        surname: "Ziccardi",
        birthday: birthday{
            day:   14,
            month: 6,
            year:  1976,
        },
    }
    usc := USContact{
        name:    "John",
        surname: "Doe",
        birthday: birthday{
            day:   16,
            month: 9,
            year:  1981,
        },
    }

    fmt.Println(euc.FullName())
    fmt.Println(euc.Birthday())

    fmt.Println(usc.FullName())
    fmt.Println(usc.Birthday())
}

Let's try to run it now:

./prog.go:40:3: unknown field 'name' in struct literal of type EUContact
./prog.go:41:3: unknown field 'surname' in struct literal of type EUContact
./prog.go:42:3: unknown field 'birthday' in struct literal of type EUContact
./prog.go:49:3: unknown field 'name' in struct literal of type USContact
./prog.go:50:3: unknown field 'surname' in struct literal of type USContact
./prog.go:51:3: unknown field 'birthday' in struct literal of type USContact

Oops: still errors.

Don't worry: we are almost there. As I said at the beginning of this post, Golang doesn't support inheritance, but we can get something similar through embedding. While when we access the values of the embedded structs we can freely use the dot notation when we initialise the struct we have to explicitly tell GO that the keys are owned by a specific struct. To make the code work, change the main as below:

func main() {
    euc := EUContact{
        contact{ // (1)
            name:    "Massimiliano",
            surname: "Ziccardi",
            birthday: birthday{
                day:   14,
                month: 6,
                year:  1976,
            },
        },
    }
    usc := USContact{
        contact{ // (2)
            name:    "John",
            surname: "Doe",
            birthday: birthday{
                day:   16,
                month: 9,
                year:  1981,
            },
        },
    }

    fmt.Println(euc.FullName()) // (3)
    fmt.Println(euc.Birthday()) // (4)
    fmt.Println(euc.name)       // (5)
    fmt.Println(usc.FullName()) // (6)
    fmt.Println(usc.Birthday()) // (7)
}

--- OUTPUT ---
Massimiliano Ziccardi
14/6/1976
Massimiliano
John Doe
9/16/1981

Finally it works as expected! Let's analyse what we did:

  • in (1) we put the initialization of the fields inherited from contact into the contact scope for EUContact.
  • in (2) we did the same for initialising USContact
  • in (3) we call on the EUContact object the method inherited from contact
  • in (4) we call the custom implementation of Birthday for the EUContact object
  • in (5) we access the inherited field name: note how we don't need to specify that it is part of contact when using the dot notation.
  • in (6) we call on the USContact object the method inherited from contact
  • in (7) we call the custom implementation of Birthday for the USContact object

So we have been able to achieve a kind of inheritance thanks to the type embedding feature of GO!