Search code examples
goembeddingsubclassing

Understanding embedding in Go


I am trying understanding Google Go's embedding mechanism (as an alternative to subclassing). Below is a simple program that summarizes my problem with the approach:

package main

import "fmt"

type Person struct {
    Name string
}

func (p *Person) Talk() {
    fmt.Println("Hi, my name is Person")
}

func (p *Person) TalkVia() {
    fmt.Println("TalkVia ->")
    p.Talk()
}

type Android struct {
    Person
}

func (p *Android) Talk() {
    fmt.Println("Hi, my name is Android")
}

func main() {
    fmt.Println("Person")
    p := new(Person)
    p.Talk()
    p.TalkVia()

    fmt.Println("Android")
    a := new(Android)
    a.Talk()
    a.TalkVia()
}

The output is:

Person
Hi, my name is Person
TalkVia ->
Hi, my name is Person
Android
Hi, my name is Android
TalkVia ->
Hi, my name is Person

but if I was subclassing (in another language), the output will be:

Person
Hi, my name is Person
TalkVia ->
Hi, my name is Person
Android
Hi, my name is Android
TalkVia ->
Hi, my name is Android

Is there a way to achieve this last output with google go embedding (not interfaces)?


Solution

  • No. Embedding is a one way thing. Embedding is slightly too unique to merely call it "syntactic sugar" -- it can be used to satisfy interfaces. Android should satisfy the interface

    type TalkViaer interface {
        TalkVia()
    }
    

    because it embeds a person. However, at its heart you have to remember that embedding is just a really clever way of giving access to a struct's member. Nothing more or less. When p is passed into TalkVia it gets a Person, and since that person has no conception of its owner, it won't be able to reference its owner.

    You can work around this by holding some owner variable in Person, but embedding is not inheritance. There's simply no conception of a "super" or an "extender" or anything like that. It's just a very convenient way to give a struct a certain method set.


    Edit: Perhaps a little more explanation is in order. But just a little.

    type Android struct {
        P person
    }
    

    We both agree that if I did a := Android{} and then a.P.TalkVia() it wouldn't call any of Android's methods, right? Even if that was Java or C++, that wouldn't make sense, since it's a member.

    Embedding is still just a member. It's just a piece of data owned by the Android, no more, no less. At a syntactic level, it confers all of its methods to Android, but it's still just a member and you can't change that.