interface

One of the subtlest design features in Go are interfaces. After reading this section, you will likely be impressed by their implementation.

What is an interface?

In short, an interface is a set of methods that we use to define a set of actions.

Like the examples in previous sections, both Student and Employee can SayHi(), but they don't do the same thing.

Let's do some more work. We'll add one more method Sing() to them, along with the BorrowMoney() method to Student and the SpendSalary() method to Employee.

Now, Student has three methods called SayHi(), Sing() and BorrowMoney(), and Employee has SayHi(), Sing() and SpendSalary().

This combination of methods is called an interface and is implemented by both Student and Employee. So, Student and Employee implement the interface: SayHi() and Sing(). At the same time, Employee doesn't implement the interface: SayHi(), Sing(), BorrowMoney(), and Student doesn't implement the interface: SayHi(), Sing(), SpendSalary(). This is because Employee doesn't have the method BorrowMoney() and Student doesn't have the method SpendSalary().

Types of Interface

An interface defines a set of methods, so if a type implements all the methods we say that it implements the interface.

type Human struct {
    name  string
    age   int
    phone string
}

type Student struct {
    Human
    school string
    loan   float32
}

type Employee struct {
    Human
    company string
    money   float32
}

func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func (h *Human) Sing(lyrics string) {
    fmt.Println("La la, la la la, la la la la la...", lyrics)
}

func (h *Human) Guzzle(beerStein string) {
    fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

// Employee overloads Sayhi
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //Yes you can split into 2 lines here.
}

func (s *Student) BorrowMoney(amount float32) {
    s.loan += amount // (again and again and...)
}

func (e *Employee) SpendSalary(amount float32) {
    e.money -= amount // More vodka please!!! Get me through the day!
}

// define interface
type Men interface {
    SayHi()
    Sing(lyrics string)
    Guzzle(beerStein string)
}

type YoungChap interface {
    SayHi()
    Sing(song string)
    BorrowMoney(amount float32)
}

type ElderlyGent interface {
    SayHi()
    Sing(song string)
    SpendSalary(amount float32)
}

We know that an interface can be implemented by any type, and one type can implement many interfaces simultaneously.

Note that any type implements the empty interface interface{} because it doesn't have any methods and all types have zero methods by default.

Value of interface

So what kind of values can be put in the interface? If we define a variable as a type interface, any type that implements the interface can be assigned to this variable.

Like the above example, if we define a variable "m" as interface Men, then any one of Student, Human or Employee can be assigned to "m". So we could have a slice of Men, and any type that implements interface Men can assign to this slice. Be aware however that the slice of interface doesn't have the same behavior as a slice of other types.

file: code/Interface/InterfaceValue/value.go

An interface is a set of abstract methods, and can be implemented by non-interface types. It cannot therefore implement itself.

Empty interface

An empty interface is an interface that doesn't contain any methods, so all types implement an empty interface. This fact is very useful when we want to store all types at some point, and is similar to void* in C.

If a function uses an empty interface as its argument type, it can accept any type; if a function uses empty interface as its return value type, it can return any type.

Method arguments of an interface

Any variable can be used in an interface. So how can we use this feature to pass any type of variable to a function?

For example we use fmt.Println a lot, but have you ever noticed that it can accept any type of argument? Looking at the open source code of fmt, we see the following definition.

This means any type that implements interface Stringer can be passed to fmt.Println as an argument. Let's prove it.

file: code/Interface/Stringer/stringer.go

Looking back to the example of Box, you will find that Color implements interface Stringer as well, so we are able to customize the print format. If we don't implement this interface, fmt.Println prints the type with its default format.

Attention: If the type implemented the interface error, fmt will call error(), so you don't have to implement Stringer at this point.

Type of variable in an interface

If a variable is the type that implements an interface, we know that any other type that implements the same interface can be assigned to this variable. The question is how can we know the specific type stored in the interface. There are two ways which I will show you.

  • Assertion of Comma-ok pattern

Go has the syntax value, ok := element.(T). This checks to see if the variable is the type that we expect, where "value" is the value of the variable, "ok" is a variable of boolean type, "element" is the interface variable and the T is the type of assertion.

If the element is the type that we expect, ok will be true, false otherwise.

Let's use an example to see more clearly.

file: code/Interface/Person/person.go

It's quite easy to use this pattern, but if we have many types to test, we'd better use switch.

  • switch test

Let's use switch to rewrite the above example.

file: code/Interface/switch/switch.go

One thing you should remember is that element.(type) cannot be used outside of the switch body, which means in that case you have to use the comma-ok pattern .

Embedded interfaces

The most beautiful thing is that Go has a lot of built-in logic syntax, such as anonymous fields in struct. Not suprisingly, we can use interfaces as anonymous fields as well, but we call them Embedded interfaces. Here, we follow the same rules as anonymous fields. More specifically, if an interface has another interface embedded within it, it will behave as if it has all the methods that the embedded interface has.

We can see that the source file in container/heap has the following definition:

We see that sort.Interface is an embedded interface, so the above Interface has the three methods contained within the sort.Interface implicitly.

Another example is the io.ReadWriter in package io.

Reflection

Reflection in Go is used for determining information at runtime. We use the reflect package, and this official article explains how reflect works in Go.

There are three steps involved when using reflect. First, we need to convert an interface to reflect types (reflect.Type or reflect.Value, this depends on the situation).

After that, we can convert the reflected types to get the values that we need.

Finally, if we want to change the values of the reflected types, we need to make it modifiable. As discussed earlier, there is a difference between pass by value and pass by reference. The following code will not compile.

Instead, we must use the following code to change the values from reflect types.

-Previous section -Next section

Last updated

Was this helpful?