Uncategorized

Embedding in Go

Go supports defining is-a relationships using an embedded type.

Fields and methods in a struct have a has-a relationship

E.g.  Person has-a name and email.

type Person struct { 
Name string
Email string
}

Embedding to define an is-a relationships  is declared with a type but no explicit field name. The unqualified type name acts as the field name.

type Employee struct {        
Person
EmployeeID string
}

This way the Person struct can be accessed using the type name:

accountant := new(Employee) 
accountant.Person.Pay()

We can also invoke any Person methods directly on the Employee object

accountant := new(Employee) 
accountant.Pay()

We cannot embed a slice or a map

type Employee struct {   
[]Person
EmployeeID string
}

This will give  a syntax error: unexpected [, expecting field name or embedded type.

A workaround will be defining a type and then embedding :

type SpecialPeople []Person 
type Employee struct {
SpecialPeople
EmployeeID string
}

The spec in Go describes embedding as below:

EmbeddedField = [ "*" ] TypeName .

Embed by-pointer

The advantage of embedding by reference is that  you are embedding all the functionality of a type without needing to know when it is instantiated. The major application of this would be to have thousands of instances sharing a single underlying data structure. This can significantly reduce memory consumption.

type Image struct {      
data [5][5]
}
type Block struct {
*Image
show bool
}

Because, the unqualified type name acts as the field name for an embedded struct, we can’t have an embedded struct and its pointer in the same struct.

type Image struct { 
T // conflicts with embedded field *T and *P.T
*T // conflicts with embedded field T and *P.T
*P.T // conflicts with embedded field T and *T
}

Promotion of fields or methods

All field or method calls for embedded type objects are resolved at compile-time without the use of a virtual table. A field or method of an embedded field in a struct is called promoted.

type Person struct {        
Name   string
Email  string
}
func (p *Person) FreeGift() int64{
return 100
}
type Employee struct {      
Person      
EmployeeID string 
}

In this example person.FreeGift()  and employee.FreeGift() is the same so the method is promoted. Promotion  occurs only at the first level.

https://play.golang.org/p/N8rXyIR_CW

However, if we have a method with the same name defined on the parent struct as below, then employee.FreeGift() will invoke the method on the Employee struct.

func (e *Employee) FreeGift() int64{        
return 200
}

In the above example, calling person.FreeGift() and employee.Person.FreeGift() will invoke the method on the Person struct

https://play.golang.org/p/lYUTijVF7U

One very important design aspect to remember is, if you plan to use an embedded struct and the embedded type has non-exported fields or methods,  those are completely inaccessible to you in a separate package.

Embedding Interfaces

Embedding an interface will add all (exported and non-exported) methods of the embedded interface to the enclosing interface.

type ImageWriter interface {        
Read(b Buffer) bool
Write(b Buffer) bool
}
type ImageFile interface {
ImageWriter  // same as adding methods of ImageWriter
Close()
}

Check out this example to add logging to your struct by embedding the log.Logger object.

https://play.golang.org/p/orTWK_TaM_W