TIL- Embedded Fields in Structs for Go

Posted by Vanessasaurus on October 12, 2021 · 6 mins read

This is a crosspost from VanessaSaurus, dinosaurs, programming, and parsnips. See the original post here.

Here is a quick “Today I learned” about how to mimic inheritance in Go. For example, in Python if we have a class, we can easily inherit attributes and functions from it for some subclass. Here is a parent class “Fruit” and a child class “Avocado” that inherits the attributes name and color, and the function IsReady.


#!/usr/bin/env python3

class Fruit:
    def __init__(self, name, color):
        self.name = name
        self.color = color

    def Announce(self):
        print(f"I am {self.color}, and I am {self.name}")


class Avocado(Fruit):

    def __init__(self, name, color, is_ripe):
        super().__init__(name, color)
        self.is_ripe = is_ripe

    def IsReady(self):
        if self.is_ripe:
            print(f"I am {self.color}, and I am {self.name}, and I am ripe!")
        else:
            print(f"I am {self.color}, and I am {self.name}, and I am NOT ripe!")    

def main():

    avocado = Avocado(name = "Harry the avocado", color = "green", is_ripe=True)
    avocado.Announce()
    # I am green, and I am Harry the avocado

    avocado.IsReady()
    # I am green, and I am Harry the avocado, and I am ripe! 
   
if __name__ == "__main__":
    main()

Notice the constructor in main - we just hand the name, color, and the variable for if it’s ripe to the Avocado class, and it works! We inherit functions and attributes from the parent class. But what about Go? I wanted an easy way to do this in Go, because otherwise creating structures with shared attributes or functionality felt very redundant. So here is the first thing I tried:

package main

import (
	"fmt"
)

type Fruit struct {
	Name string
	Color	string
}

func (f* Fruit) Announce() {
	fmt.Printf("I am %s, and I am %s\n", f.Color, f.Name)
}

type Avocado struct {
	Fruit
	IsRipe bool
}

func (a * Avocado) IsReady() {
	if a.IsRipe {
		fmt.Printf("I am %s, and I am %s, and I am ripe!\n", a.Color, a.Name)
	} else {
		fmt.Printf("I am %s, and I am %s, and I am NOT ripe!\n", a.Color, a.Name)
	}

}

func main() {
	// avocado := Avocado{Name: "Harry the avocado", Color: "green", IsRipe: true}	
	avocado.Announce()
	avocado.IsReady()
}

And that totally didn’t work! I saw this:

./main.go:19:21: cannot use promoted field Fruit.Name in struct literal of type Avocado
./main.go:19:48: cannot use promoted field Fruit.Color in struct literal of type Avocado

Turns out, I was close. I actually needed to provide Fruit in the constructor for Avocado, like this:

// Instead, pass the "base" type to the underlying type
avocado := Avocado{Fruit: Fruit{Name: "Harry the avocado", Color: "green"}, IsRipe: true}

Is this kind of funky? Yeah, and others think so too. But I’m really grateful for the functionality because I can create a bunch of different struct types that have most in common, but maybe don’t share a few things. Could there be other issues that arise? Maybe. I haven’t hit them yet. :) There’s a nice article here that I’m perusing to learn more. Here is the entire set of files in a gist if you want to play around.