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:
and birthday will be composed by:
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:
contact structtype EUContact contact and type USContact contactEUContact and USContact have no FullName methodIf you look at the declaration for the FullName method, its receiver is *contact, but:
EUContact and USContactExplanation 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:
contact into the contact scope for EUContact.USContactEUContact object the method inherited from contactBirthday for the EUContact objectname: note how we don't need to specify that it is part of contact when using the dot notation.USContact object the method inherited from contactBirthday for the USContact objectSo we have been able to achieve a kind of inheritance thanks to the type embedding feature of GO!