In Go, both structs and interfaces are used to create custom types that can associate a collection to methods. Unlike struct, which helps in creating concrete types, interface creates abstract types. Concrete types are particularly used for representing its values by exposing its methods. They expose their behavior through a set of methods. Interfaces, on the other hand, create a custom type that is completely abstract. This means that they cannot be instantiated like struct. That, however, does not stop us from creating interface type variables. Interestingly, the abstract types are used to assign any concrete types. Interfaces do not expose their internal structure such as values or all the operations they support. In fact, an interface value does not even allow its client to know what it is – the behavior is only exposed by its methods.
Declaring an Interface in Go
The interface type specifies a set of methods and is instantiated only at the hand of concrete types. For example, in the Go API, the most common type of interface we work with is Writer, which is declared in the io package. It abstracts all the types through which we can write bytes information to files, buffers, sockets, HTTP clients, and so forth. Similarly, there is an interface called Reader, which abstracts all the read operations in bytes. There is another interface called Closer in the same io package that abstracts the close operation of byte transactions in association with a file operation or network connection.
An interface can be declared as follows.
type circle interface {
area() float64
circum() float64
}
Interface’s name is enclosed between the two keywords – type and interface. There can be zero or more method signatures declared within the curly braces.
Implementing interface in Go
All the methods declared inside the interface must be implemented; otherwise, there will be a compilation error. An interface simply declared is of no value unless some custom types have been created. The interface comprises the methods required by it. For example, we may use the interface declared above as follows:
package main
import (
"fmt"
"math"
)
type circle interface {
area() float64
circum() float64
}
type mycircle struct {
radius float64
}
func (m mycircle) area() float64 {
a := math.Pi * math.Pow(m.radius, 2)
return a
}
func (m mycircle) circum() float64 {
c := 2 * math.Pi * m.radius
return c
}
func main() {
var c1 circle
c1 = mycircle{5.67}
fmt.Println("Area of mycircle is ", c1.area())
fmt.Println("Circumeference of mycircle is ", c1.circum())
}
Notice how the methods declared in the interface are actually implemented by the concrete type struct as if the method is a part of the struct. The important thing is that if there is another concrete type that wants to implement the methods declared in the interface in a different manner, then it may do so as follows:
package main
import (
"fmt"
"math"
)
type circle interface {
area() float64
circum() float64
}
type myellipse struct {
xaxis float64
yaxis float64
}
func (e myellipse) area() float64 {
a := e.xaxis * e.yaxis * math.Pi
return a
}
func (e myellipse) circum() float64 {
c := 2 * math.Pi * math.Sqrt(math.Pow(e.xaxis, 2)+math.Pow(e.yaxis, 2))
return c
}
type mycircle struct {
radius float64
}
func (m mycircle) area() float64 {
a := math.Pi * math.Pow(m.radius, 2)
return a
}
func (m mycircle) circum() float64 {
c := 2 * math.Pi * m.radius
return c
}
func main() {
var c1 circle
c1 = mycircle{5.67}
fmt.Println("Area of mycircle is ", c1.area())
fmt.Println("Circumeference of mycircle is ", c1.circum())
var c2 circle
c2 = myellipse{4.5, 6.7}
fmt.Println("Area of myellipse is ", c2.area())
fmt.Println("Circumeference of mycircle is ", c2.circum())
}
Here we have included two more methods specific to myellipse type. This clearly illustrates the use of interfaces where it establishes a generic blueprint for many different types of implementation. As long as all the methods are implemented, an interface type can be used to assign any other type of variable. In case an interface is a superset of another interface, the casting of superset to subset is valid, so is casting from a structure type to an interface.
The idea resembles one thing that we typically do with C++ templates that can be done easily with interfaces in Go. Although template functions and classes are part of the C++ framework, something which Go does not support, yet with an interface, we may define the methods and use them like templates. The syntactical statements in Go are much simpler than C++ templates.
Multiple Interfaces in Go
We can create a concrete struct type from multiple interfaces and reap the benefit of implementing different methods from multiple interfaces. Here is a quick example:
package main
import "fmt"
type dawn interface {
morning() string
}
type dusk interface {
night() string
}
type greet struct {
prefix string
}
func (g greet) morning() string {
return g.prefix + " morning!"
}
func (g greet) night() string {
return g.prefix + " night."
}
func main() {
a := greet{"Good"}
var m dawn = a
var n dusk = a
fmt.Println(m.morning())
fmt.Println(n.night())
//error
//fmt.Println(m.night())
//fmt.Println(n.morning())
}
Note how we have created dawn and dusk interfaces with method signatures such as morning and night declared respectively into them. The struct type greet implements both the methods from the interfaces. In main, we have assigned the value of struct type into the variables of dawn and dusk and invoked its respective methods. Interestingly, if we reverse the call, such as invoking a method declared in dawn with dusk variable or vice versa, the call is not allowed. This is because the variables are of static type, and if we want to make it work, we have to dynamically extract the value using types that implement both these methods, such as greet in this case.
Empty Interfaces in G
When an interface does not contain any method signature in its declaration, it is called an empty interface. These types of interfaces have some special uses. The declaration is as simple as follows:
interface{}
Recall that there are some built-in functions such as Println in the fmt package that not only accepts different types of values as arguments but also the different number of arguments. These types of functions are called variadic functions. The Go uses an empty interface to implement these techniques. The signature of the Println function in Go is as follows:
func Println(a ...interface{})(n int, err, error)
The example below illustrates how we can use an empty interface to make a function accept any type of argument.
package main
//...same as above
func acceptanytype(i interface{}) {
fmt.Printf("Function type = %T, Value = %v\n", i, i)
}
func main() {
var c1 circle
c1 = mycircle{5.67}
var c2 circle
c2 = myellipse{4.5, 6.7}
acceptanytype("Hello")
acceptanytype(123.56)
acceptanytype(c1)
acceptanytype(c2)
}
Output
Function type = string, Value = Hello
Function type = float64, Value = 123.56
Function type = main.mycircle, Value = {5.67}
Function type = main.myellipse, Value = {4.5 6.7}
Here we have passed a string, float, and struct type as argument to the acceptanytype function. The function simply prints the function type and the value. Observe how the function dynamically changes its type, and we get a polymorphic behavior according to the value passed. This is the beauty of the empty interface.
Embedding an Interface in Go
In Go, there is no concept of extending an interface. But we definitely can create a new interface embedding more than one interface. So, let us create another interface called twilight that embeds both the dawn and dusk interface as follows:
//...
type twilight interface {
dawn
dusk
}
//...
func main() {
a := greet{"Good"}
var t twilight = a
fmt.Println(t.night())
fmt.Println(t.morning())
}
Now, since the twilight interface embeds both dawn and dusk interface, we can assign greet struct type to the twilight variable and invoke both the methods without any problem.
Final Thoughts
Interfaces are, therefore, a name given to a collection of method signatures. Here the idea is to create an abstract entity by encompassing a set of methods. It has many uses, here we tried to provide only a glimpse of its uses. It is a powerful and versatile concept that is pretty simple to use and understand. It is often used to make a method or function polymorphic. Embedding interfaces make it easy to work with multiple interfaces. Also, interfaces are useful where we need a dynamic type of argument, such as the function Println.