Go-tcha: Shallow Polymorphism
Tags: go, pitfall
Published: 2022-06-25 15:30:41

---
title: Go-tcha: Shallow Polymorphism
published: true
description: Why Go isn’t object oriented like most other object oriented languages.
tags: #go #pitfall
---

## Introduction

Polymorphism (or, more specifically, [subtype polymorphism](https://en.wikipedia.org/wiki/Subtyping)) is one of the characteristics of an [object-orient programming language](https://en.wikipedia.org/wiki/Object-oriented_programming) (OOP) and is the ability of functions written to operate on objects of a particular type to operate on objects of subtypes also, sometimes doing different things.

## Conventional Inheritance & Polymorphism

Consider the following example in C++:

```cpp
struct B {
    void F() {
        G();
    }

    virtual void G() {
        std::cout << "B::G()\n";
    }
};

struct D : B {
    void G() override {
        std::cout << "D::G()\n";
    }
};

int main() {
    B b;
    D d;
    B *p;

    p = &b;
    p->F();
    p = &d;
    p->F();
}
```

In C++ parlance, `B` is a _base type_ and `D` is _derived type_ (from `B`).  (Or in conventional OOP parlance, `B` is a super-type and `D` is a subtype.)

`B` defines two member functions (methods): `F()` and `G()`.

> In C++, the presence of `virtual` allows derived types to `override` what a base type’s function does.

`D` inherits `F()` from `B`, but overrides `G()` to do something different from `B`’s `G()`.

Compiling and running the C++ program produces the output:
```txt
B::G()
D::G()
```
that surprises nobody familiar with C++.  Specifically, even though `F()` (implemented only in `B`) calls `G()` (also implemented in `B`), for the `d` object, it’s `D`’s `G()` that actually gets called since `D` overrode `B`’s `G()`.  The particular `G()` called by `F()` is determined _dynamically_ (at run-time) based on the type of the object even though the function was called via a base type pointer `B`.

> Equivalent programs can be easily written in C# and Java that produce the same output.

## Embedding in Go

Go doesn’t have the concept of inheritance.  Instead, it uses _embedding_, that is instead of `D` inheriting from `B`, `D` embeds a `B` inside it.  The equivalent types in Go would be declared as:

```go
type B struct {
}

type D struct {
    B            // a B is embedded inside D
}

func (b *B) F() {
    b.G()
}

func (b *B) G() {
    fmt.Println("B::G()")
}

func (d *D) G() {
    fmt.Println("D::G()")
}
```

Despite being embedded, you can still call methods implemented only for `B` on an object of `D`:

```go
var d D
d.F()    // works
```

So it _looks_ like `D` inherits `F()` from `B`; but the Go compiler adds a bit of [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) by automatically synthesizing a forwarding method for you:
```go
func (d *D) F() {  // synthesized by the Go compiler
    d.B.F()        // calls F() on the B object embedded in D
}
```
That is, the compiler automatically forwards unimplemented methods to embedded objects on your behalf (because having to do so manually would be tedious).

So if it _looks_ (sort of) like inheritance and works like inheritance, is that good enough?  Is it merely semantic quibbling _not_ to call it inheritance?

Let’s try to convert the last bit of the C++ program to Go:

```go
func main() {
    var b B
    var d D
    var p *B

    p = &b;
    p.F()
    p = &d;  // line 34
    p.F()
}
```

Compiling this produces:
```txt
example.go:34:7 cannot use &d (type *D) as type *B in assignment
```

In OOP parlance, just because `D` “has-a” `B` embedded inside it doesn’t mean `D` “is-a” `B`.  Specifically in Go, you can’t use a pointer to an embedded type (here, `B`) to point at a type into which an embedded type is embedded (here, `D`).  In Go, pointers can only ever point to objects of their declared _static_ (compile-time) type.

## Interfaces

To get polymorphism in Go, you need an `interface`:

```go
type I interface {
    F()
    G()
}

func main() {
    var b B
    var d D
    var i I  // interface variable now, not *B

    i = &b;
    i.F()
    i = &d;
    i.F()
}
```

Here, we’ve added an `interface` that has the two methods `F()` and `G()` we’ve been using.  A variable of an interface (here, `i`) is like a pointer, but also can do polymorphism.  It can point to any object that implements it, so the code now compiles.

> In Go, any type that just so happens to implement `F()` and `G()` (with matching signatures) automatically implements the interface: no explicit `implements` keyword is used (as it is in Java).
>
> Astute readers will realize that, unlike C++ where inheritance is a prerequisite for polymorphism, embedding and polymorphism are orthogonal in Go.  This has some advantages (often espoused by Go proponents), but it comes at a price.

However, running the Go program produces the output:
```txt
B::G()
B::G()
```
that surprises anybody familiar with C++, C#, or Java, because `D`’s `G()` is _not_ called. Why not? Because as mentioned, in Go, polymorphism can happen _only_ for interfaces, not types.  But we _are_ using an interface, so why doesn’t it produce the same output as the C++ program?

Even though `F()` was _called_ via an interface:
```go
    i = &d;
    i.F()
```
the declaration of `F()`:
```
func (b *B) F() {
    b.G()
}
```
has the receiver declared as `*B` (pointer to type `B`) and _not_ an interface because a function’s receiver _must_ be a type in Go.  So even though `F()` was _called_ on an object via an interface, when the object is _received_ by `F()`, it’s as if the interface “decayed” into a pointer.

Now a pointer, the only methods that can _ever_ be called on `b` from within `F()` are those of `B` and _never_ of any “derived” type — even when `b` points to a `B` object embedded in a `D` as is the case here.

Once an interface “decays” into a pointer, there’s no way back: there’s no way to recover the original interface.  Hence, polymorphism in Go is “shallow” in that it goes one call level down from an interface to a pointer and no further.

## Conventional Polymorphism in Go

Is there any way to get conventional polymorphism in Go?  One way to do it is if we clone `B`’s `F()`, but for `D`:
```go
func (d *D) F() {
    d.G()          // not d.B.G() as in synthesized F()
}
```
(which will suppress the compiler from synthesizing a forwarding method) and re-run the program, it produces the output:
```txt
B::G()
D::G()
```
matching the C++ program because the receiver is `*D` now so `D`’s `G()` is called.  So if you want more conventional polymorphism in Go, one way to do it is by overriding _every_ method even if does the exact same thing as the “base type.”  That’s really tedious.  There’s at least [one other way](https://gist.github.com/strickyak/4201971), but that’s just as tedious if not more so.

## Conclusion

Shallow polymorphism in Go is a consequence of the deliberate language design choices made, in particular the orthogonality of inheritance and polymorphism and the use of an interface as the mechanism for polymorphism rather than a _vptr_ (virtual table pointer) as used in C++.  As mentioned, it has some advantages, but it makes writing programs that use non-trivial object hierarchies and polymorphism a lot harder to write.