Skip to content

Interface

In Go language, an interface is an abstract type used to define a set of method signatures without providing method implementations. The core concept of interface is to describe behavior, and the specific behavior implementation is provided by the type that implements the interface. Interfaces are widely used in Go language to achieve polymorphism, loose coupling, and code reuse.

Concept

There is a watershed in Go's development history regarding interfaces. Before Go 1.17, the official reference manual defined an interface as: a set of methods.

An interface type specifies a method set called its interface.

The definition of interface implementation was:

A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface

Translated: when a type's method set is a superset of an interface's method set, and values of that type can be stored by variables of that interface type, then that type is said to implement the interface.

However, in Go 1.18, the definition of interface changed. An interface is now defined as: a set of types.

An interface type defines a type set.

The definition of interface implementation is:

A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface

Translated: when a type is located within an interface's type set, and values of that type can be stored by variables of that interface type, then that type is said to implement the interface. Additionally, the following definition was provided:

Type T can be said to implement interface I when:

  • T is not an interface and is an element in interface I's type set
  • T is an interface and T's type set is a subset of interface I's type set

If T implements an interface, then T's values also implement that interface.

The biggest change in Go 1.18 was the addition of generics. The new interface definition serves generics, but it doesn't affect the previous usage of interfaces. Interfaces are now divided into two categories:

  • Basic Interface: An interface that only contains method sets is a basic interface
  • General Interface: An interface that contains type sets is a general interface

A method set is a collection of methods. Similarly, a type set is a collection of types.

TIP

You might find these concepts obscure and hard to understand, but actually you don't need to understand all the above at all.

Basic Interface

As mentioned earlier, a basic interface is a method set, which is a collection of methods.

Declaration

Let's first see what an interface looks like.

go
type Person interface {
  Say(string) string
  Walk(int)
}

This is a Person interface with two exposed methods Walk and Say. In an interface, function parameter names are no longer important. Of course, adding parameter names and return value names is also allowed.

Initialization

An interface alone cannot be initialized because it's just a set of specifications without concrete implementation, but it can be declared.

go
func main() {
   var person Person
   fmt.Println(person)
}

Output:

 <nil>

Implementation

Let's look at an example. A construction company wants a special type of crane, so they provide specifications and drawings for the crane, specifying that the crane should have lifting and hoisting functions. The construction company doesn't build the crane, they just provide specifications - this is called an interface. Company A takes the order and builds a magnificent crane using their proprietary technology and delivers it to the construction company. The construction company doesn't care about the technology used or the magnificent crane - as long as it can lift and hoist, they just treat it as an ordinary crane. Providing specific functionality according to specifications is called implementation. Using functionality only according to interface specifications while shielding internal implementation is called interface-oriented programming. After some time, the magnificent crane breaks down and Company A goes out of business. Company B builds an even more powerful giant crane according to the specifications. Since it also has lifting and hoisting functions, it seamlessly replaces the magnificent crane without affecting construction progress. The building is successfully completed. Internal implementation changes while functionality remains unchanged, not affecting previous usage and allowing arbitrary replacement - this is the benefit of interface-oriented programming.

Next, we'll use Go to describe the above scenario:

go
// Crane interface
type Crane interface {
  JackUp() string
  Hoist() string
}

// CraneA
type CraneA struct {
  work int // Different internal fields represent different internal details
}

func (c CraneA) Work() {
  fmt.Println("Using technology A")
}
func (c CraneA) JackUp() string {
  c.Work()
  return "jackup"
}

func (c CraneA) Hoist() string {
  c.Work()
  return "hoist"
}

// CraneB
type CraneB struct {
  boot string
}

func (c CraneB) Boot() {
  fmt.Println("Using technology B")
}

func (c CraneB) JackUp() string {
  c.Boot()
  return "jackup"
}

func (c CraneB) Hoist() string {
  c.Boot()
  return "hoist"
}

type ConstructionCompany struct {
  Crane Crane // Only store cranes according to Crane type
}

func (c *ConstructionCompany) Build() {
  fmt.Println(c.Crane.JackUp())
  fmt.Println(c.Crane.Hoist())
  fmt.Println("Construction completed")
}

func main() {
  // Using CraneA
  company := ConstructionCompany{CraneA{}}
  company.Build()
  fmt.Println()
  // Switch to CraneB
  company.Crane = CraneB{}
  company.Build()
}

Output:

Using technology A
jackup
Using technology A
hoist
Construction completed

Using technology B
jackup
Using technology B
hoist
Construction completed

In the above example, you can observe that interface implementation is implicit, which corresponds to the official definition of basic interface implementation: the method set is a superset of the interface's method set. So in Go, implementing an interface doesn't require the implements keyword to explicitly specify which interface to implement. As long as all methods of an interface are implemented, the interface is implemented. After having an implementation, the interface can be initialized. The construction company struct declares a Crane type member variable internally, which can store all values that implement the Crane interface. Since it's a Crane type variable, the only accessible methods are JackUp and Hoist. Other internal methods like Work and Boot cannot be accessed.

As mentioned before, any custom type can have methods. According to the definition of implementation, any custom type can implement interfaces. Here are a few special examples:

go
type Person interface {
   Say(string) string
   Walk(int)
}

type Man interface {
   Exercise()
   Person
}

The Man interface's method set is a superset of Person, so Man also implements the Person interface, though this is more like "inheritance".

go
type Number int

func (n Number) Say(s string) string {
  return "bibibibibi"
}

func (n Number) Walk(i int) {
  fmt.Println("can not walk")
}

The underlying type of Number is int. Although this seems outrageous in other languages, Number's method set is indeed a superset of Person, so it counts as an implementation.

go
type Func func()

func (f Func) Say(s string) string {
  f()
  return "bibibibibi"
}

func (f Func) Walk(i int) {
  f()
  fmt.Println("can not walk")
}

func main() {
  var function Func
  function = func() {
    fmt.Println("do somthing")
  }
  function()
}

Similarly, function types can also implement interfaces.

Empty Interface

go
type Any interface{

}

The Any interface has no method set internally. According to the definition of implementation, all types are implementations of the Any interface because all types' method sets are supersets of the empty set. So the Any interface can store values of any type.

go
func main() {
  var anything Any

  anything = 1
  println(anything)
  fmt.Println(anything)

  anything = "something"
  println(anything)
  fmt.Println(anything)

  anything = complex(1, 2)
  println(anything)
  fmt.Println(anything)

  anything = 1.2
  println(anything)
  fmt.Println(anything)

  anything = []int{}
  println(anything)
  fmt.Println(anything)

  anything = map[string]int{}
  println(anything)
  fmt.Println(anything)
}

Output:

(0xe63580,0xeb8b08)
1
(0xe63d80,0xeb8c48)
something
(0xe62ac0,0xeb8c58)
(1+2i)
(0xe62e00,0xeb8b00)
1.2
(0xe61a00,0xc0000080d8)
[]
(0xe69720,0xc00007a7b0)
map[]

Through the output, you'll notice the two outputs are inconsistent. Actually, an interface can be seen internally as a tuple composed of (val,type). type is the concrete type, and when calling methods, it calls the concrete value of the concrete type.

go
interface{}

This is also an empty interface, but an anonymous empty interface. In development, anonymous empty interfaces are usually used to represent accepting values of any type. An example is as follows:

go
func main() {
   DoSomething(map[int]string{})
}

func DoSomething(anything interface{}) interface{} {
   return anything
}

In subsequent updates, the official team proposed another solution. For convenience, you can use any to replace interface{}. Both are completely equivalent because the former is just a type alias:

go
type any = interface{}

When comparing empty interfaces, their underlying types are compared first. If the types don't match, it's false, then the values are compared. For example:

go
func main() {
  var a interface{}
  var b interface{}
  a = 1
  b = "1"
  fmt.Println(a == b)
  a = 1
  b = 1
  fmt.Println(a == b)
}

Output:

false
true

If the underlying type is not comparable, it will panic. For Go, the comparability of built-in data types is as follows:

TypeComparableBasis
NumericYesWhether values are equal
StringYesWhether values are equal
ArrayYesWhether all elements are equal
SliceNoNot comparable
StructYesWhether all field values equal
MapNoNot comparable
ChannelYesWhether addresses are equal
PointerYesWhether stored addresses equal
InterfaceYesWhether stored data is equal

In Go, there's a special interface type used to represent all comparable types, which is comparable:

go
type comparable interface{ comparable }

TIP

If you try to compare incomparable types, it will panic

General Interface

General interfaces are designed for generics. Once you master generics, you master general interfaces. Please refer to Generics

Golang by www.golangdev.cn edit