Embedding in Go
- Monday December 30 2013
- golang embedding inheritance object-oriented c++
I've recently started on a new project in Go. I've found myself considering the implications of many of the language features recently. By far, the feature that is most interesting feature is the embedding of structs within each other.
Types in go
Go supports user defined types in the form of a struct
which can have zero or more fields.
type Discount struct { percent float32 startTime uint64 endTime uint64 }
This gives a type Discount
which can be instantiated. Furthermore, you can add methods which receive the type.
func (d Discount) Calculate(originalPrice float32) float32{ return originalPrice*d.percent }
Presuming you have an instance of Discount
called holidaySale
you can invoke the method using holidaySale.Calculate(49.99)
. But methods are not limited to receiving types only by value.
func (d *Discount) IsValid(currentTime uint64) bool{ return currentTime > d.startTime && currentTime < d.endTime }
The invocation is identical despite the differing of the receiver type holidaySale.IsValid(100.0)
. Golang transparently converts the value to a pointer when the method is invoked. The main reason to have a method receive a pointer type is so the method can mutate the referenced value, but it also can avoid a potentially costly copy.
Embedding
Types can also have zero or more anonymous fields.
type PremiumDiscount struct{ Discount //Embedded additional float32 }
Including an anonymous field in a struct
is known as embedding. In this case the Discount
type is embedded in the PremiumDiscount
type. All the methods of Discount
are instantly available on the PremiumDiscount
type. Furthermore those same methods can be hidden
func (d *PremiumDiscount) Calculate(originalPrice float32) float32 { return d.Discount.Calculate(originalPrice) - d.additional }
In this example the Calculate
method receives a pointer to a PremiumDiscount
. It calls the Calculate
method that receives a pointer to a Discount
and subtracts an additional amount off of it. This is possible because anonymous fields can still be referenced as part of the struct
by using the full name of the embedded struct.
While this may all look very similiar to inheritance and method overriding in a language like Java, it is only superficially similar.
By-Value
The most logical way to embed a struct
is by-value. This is similar, but not equivalent to the inheritance mechanism in an object oriented language.
type Parent struct{ value int64 } func (i *Parent) Value() int64{ return i.value } type Child struct{ Parent multiplier int64 } func (i Child) Value() int64{ return i.value * i.multiplier }
In the context of the above example, a call to myParent.Value()
returns the value
field if myParent
is an instance of Parent
. Using a similarly named instance of Child
, calling myChild.Value()
returns the product of the value
and multiplier
fields. This is fundamentally different than method overriding because a call to myChild.Parent.Value()
still invokes the method receiving a *Parent
, not the one receiving Child
. Both method calls are resolved at compile-time without using any sort of virtual table.
There is nothing explicitly bad about this. Consider the following interface in the context of this example
type Valueable interface { Value() int64 }
This interface requires a type to implement a Value
method. The Parent
type meets this interface. Since the Child
type embeds the Parent
type it also meets this interface. However, if an instance of Child
is used from a context referring to it as a Valueable
, there is no easy way to invoke the Value
method receiving a *Parent
. It is still possible by using type assertions or reflection.
There is an important distinction to observe here. If myParent
is an instance of Parent
the value myParent
cannot act as a Valueable
. You must use the the value &myParent
, a pointer to the instance, to act as a Valueable
. This is because the method Value
receives a *Parent
not a Parent
.
Although the value myParent
cannot satisfy the interface Valueable
, the value myChild
can! This is because the Value
method receives a Child
by value. The pointer &myChild
satisfies the interface as well. You may not actually want to use myChild
to satisfy the interface as it creates a copy. This means that any further changes to myChild
would not be seen by anything using the interface.
Using Child
and Parent
to satisfy the Valueable
interface is still not traditional inheritance and method overriding. You could argue that this is example is equivalent to this C++ example
class Valueable{ public: virtual int Value() = 0; };
You could go on to define a class
named Parent
and another one named Child
. But Parent
would need to explicitly inherit from Valueable
, which is not the case with interfaces in Golang. The most prominent difference is that there is no way that usage of a Valueable*
abstract type could result in the copy-on-assignment semantics that are possible with interfaces in Go.
Exploring this further, the Child
class could not implement any other abstract classes in C++ without inheriting from them. At first this seems like a difference of syntactic sugar. In C++ you are required to explicitly indicate what abstract classes you inherit from. In Go, you either meet the interface or you do not. The compiler has to check that you actually do this in either language, so Go gains some brevity by using this. But also, you have the possibility of defining an interface to code you don't maintain or cannot otherwise change. As an author, I could write many different types which all implement the Value
method. At any point in time, someone else could create the Valueable
interface and use instances of my types to satisfy their interface without ever requiring me to change my code. This gives you the ability to use a type from one framework in another framework without needing a wrapper type and a large number of wrapper functions. In C++ this difference is irreconcilable because of the fact that one framework defines and implements an Foo::Reader
and the other one implements a Bar::Readable
. The two types could have exactly the same read
function, but you'd still need a bunch of glue to make one library work with another.
The real problem with comparing Go interfaces to abstract classes in C++ is that eventually you wind up having to deal with diamond inheritance at some point. We're all capable of dissecting the hundreds of lines of compiler errors that can result from some situation of ambigious parent classes, but at the end of the day all that effort doesn't actually get you any closer to solving the problem at hand. Of course Java "solves" this problem by declaring thou shalt not have multiple inheritance. But that doesn't really slow anyone down as they add sixteen different interface specifications to a single type.
You know not what you do(literally)
There is still a fundamental issue with by-value embedding in Go. If the type you embed has non-exported fields and you are in a separate package, those fields are completely inaccessible to you. Sure, you can embed a HTMLElementTree
, but if you cannot initialize the internals of it you have no idea what it is good for. Furthermore, the type embedding HTMLElementTree
gains a bunch of methods which can be invoked which are either useless or dereference nil
when called.
A common idiom in Go is to define a NewX
method which returns a pointer to a ready to use instance. For example the included json
package in Go defines NewDecoder
for decoding JSON from streams. But since NewDecoder
returns *Decoder
you are stuck with a pointer. You could dereference it and create a copy during the initialization of something that embeds it, but that would result in an object being created on the heap with a very short lifecycle. There is nothing prohibiting the creation of Init
methods that receives a pointer to a type and does all the work needed to set it up.
All of this leads to a very strong arugment that exported types in Go should be useful by default. RAII is a sound concept, but the complexity that it ultimately leads to in C++ is not enviable.
All this consideration has assumed that you control the instantiation of what is embedded. Proceed on to the next section.
By-Pointer
You can embed by-pointer in go
type Bitmap struct{ data [4][4]bool } type Renderer struct{ *Bitmap //Embed by pointer on uint8 off uint8 } func (r *Renderer)render() { for i := range(r.data){ for j := range(r.data[i]){ if r.data[i][j] { fmt.Printf("%c",r.on) } else { fmt.Printf("%c",r.off) } } fmt.Printf("\n") } }
The Renderer
type embeds a Bitmap
by-pointer. The first advantage to this is that you can rely on functions that use the NewX
idiom returning a struct
by-pointer to do initialization. The second advantage is that you can embed all the functionality of a type without needing to know when it is instantiated. The embedded pointer to a Bitmap
is no different than any other pointer in Go, so it can be assigned multiple times. By doing this you can change what instance you are extending dynamically at run time.
In this example, a single instance of Bitmap
can act as the embedded instance of many Renderer
instances
var renderA,renderB Renderer renderA.on = 'X' renderA.off = 'O' renderB.on = '@' renderB.off = '.' var pic Bitmap pic.data[0][1] = true pic.data[0][2] = true pic.data[1][1] = true pic.data[2][1] = true pic.data[3][1] = true renderA.Bitmap = &pic renderB.Bitmap = &pic renderA.render() renderB.render()
This shares the same Bitmap
instance to two different renderers. Each renderer has its own set of characters, allowing two representations of the bitmap to be printed. This is what the output looks like.
OXXO OXOO OXOO OXOO .@@. .@.. .@.. .@..
This example demonstrates the Flyweight Pattern. Although inconsequential to memory consumption in this example, having many thousands of instances sharing a single underlying data structure can be very significant in reducing the memory consumption of systems.
Applying this to Interfaces
User defined types in Go are not restricted to embedding just a struct
. They can actually embed an interface
type as well.
One consequence of this is that anything embedding an interface
satisfies that interface. You can use this to create objects which add behavior to an existing interface.
package main import "io" import "os" import "fmt" type echoer struct{ io.Reader //Embed an interface } func (e * echoer) Read(p []byte) (int, error) { amount, err := e.Reader.Read(p) if err == nil { fmt.Printf("Overridden read %d bytes:%s\n",amount,p[:amount]) } return amount,err } func readUpToN(r io.Reader, n int) { buf := make([]byte,n) amount, err := r.Read(buf[:]) if err == nil { fmt.Printf("Read %d bytes:%s\n",amount,buf[:amount]) } } func main(){ readUpToN(os.Stdin,3) var replacement echoer replacement.Reader = os.Stdin readUpToN(&replacement,3) }
In this example, the embedded interface is io.Reader
. The os.Stdin
object is the standard input stream and satisfies that interface. Calling readUpToN
the first time reads and displays 3 bytes. The second call to readUpToN
uses an instance of echoer
that embeds os.Stdin
. Since there is a receiver method for the *echoer
type it is called. In the example it also displays the text. The result looks like this.
ericu@luit:~$ echo 'ABCDEF' | ~/go/bin/embedinterface Read 3 bytes:ABC Overridden read 3 bytes:DEF Read 3 bytes:DEF
There is no such thing as embedding an interface
by-pointer. Trying to do something such as
type echoer struct{ *io.Reader //Embedding an interface by pointer }
generates the following error
../go/src/hydrogen18.com/embedinterface/main.go:8: embedded type cannot be a pointer to interface
Conclusion
Go offers the embedding mechanism as an alternative to the inheritance mechanism in traditional object oriented programming languages. I feel it offers the correct level of flexibility without levying additional mountains of complexity on the language. I will not really know how I feel about Go until I complete this project, but this has been a terrific educational experience.