Methods interfaces and embedded types in the Go language

  • 2020-05-10 18:21:17
  • OfStack

An overview of the

In the Go language, what happens if one structure and one embedded field implement the same interface at the same time? Let's take a guess 1. There are two possible questions:

1. Does the compiler report errors because we have two interface implementations at the same time?
2. If the compiler accepts such a definition, how does the compiler determine which implementation to use when the interface is called?

After writing some test code and reading the standards in depth, I found something interesting and felt compelled to share it. Let's start with the methods in the Go language.

methods

The Go language has both functions and methods. A method is a function that contains a receiver, either a value of a named type or a struct type or a pointer. All methods of a given type belong to the set of methods of that type.

The following defines a struct type and a method for that type:


type User struct {
  Name  string
  Email string
} func (u User) Notify() error

First we define a struct type called User, and then we define a method of this type called Notify, whose receiver is a value of type User. To call the Notify method we need a value or pointer of type User:


// User A value of type can call a method whose recipient is a value
damon := User{"AriesDevil", "ariesdevil@xxoo.com"}
damon.Notify() // User A pointer to a type can also call a method whose recipient is a value
alimon := &User{"A-limon", "alimon@ooxx.com"}
alimon.Notify()

When we use Pointers in this example, Go adjusts and unreferences Pointers so that the call can be executed. Note that when the recipient is not a pointer, the method corresponding to a copy of the receiver's value (meaning that even if you use the pointer call function, but the function of the receiver is a value type, so the function of internal operation or to the operation of the copy, rather than pointer manipulation, and see: http: / / play golang. org/p/DBhWU0p1Pv).

We can modify the Notify method to make its recipient use the pointer type:


func (u *User) Notify() error

1 time again before the call (note: when the receiver is a pointer, use the function called internal value type is also to the operation of the pointer, see: http: / / play golang. org/p/SYBb4xPfPh) :


// User A value of type can call a method whose recipient is a pointer
damon := User{"AriesDevil", "ariesdevil@xxoo.com"}
damon.Notify() // User A pointer of type can also call a method whose recipient is a pointer
alimon := &User{"A-limon", "alimon@ooxx.com"}
alimon.Notify()

If you're not sure exactly when to use a value and when to use a pointer as a receiver, you can read this introduction to 1. This article also includes how recipients of community conventions should be named.

interface

The interfaces in the Go language are unique and offer incredible series 1 flexibility and abstraction. They specify that a particular type of value and pointer behave in a particular way. From a language point of view, an interface is a type that specifies a set of methods, all of which are considered interfaces.

Here is the definition of an interface:


type Notifier interface {
  Notify() error
}

We defined an interface called Notifier and included an Notify method. When an interface contains only one method, the suffix -er is added when naming the interface according to the Go language convention. This convention is useful, especially when interfaces and methods have the same name and meaning.

We can define as many methods as possible in the interface, but in the Go language standard library, it's hard to find an interface that contains more than two methods.

Implementing an interface

The Go language is a special one when it comes to how we want our types to implement interfaces. The Go language does not require that we explicitly implement the type of interface. If all the methods in an interface are implemented by our type, we say that the type implements the interface.

Let's continue with the previous example and define 1 function to accept the value or pointer of any 1 type that implements the interface Notifier:


func SendNotification(notify Notifier) error {
  return notify.Notify()
}

The SendNotification function calls the Notify method, which is passed in a value or pointer implementation of the function. One or more functions can then be used to perform any one of the values or Pointers that implement the interface.

Implement the interface with our User type and pass in a value of User type to call the SendNotification method:


func (u *User) Notify() error {
  log.Printf("User: Sending User Email To %s<%s>\n",
      u.Name,
      u.Email)
  return nil
} func main() {
  user := User{
    Name:  "AriesDevil",
    Email: "ariesdevil@xxoo.com",
  }
 
  SendNotification(user)
} // Output:
cannot use user (type User) as type Notifier in function argument:
User does not implement Notifier (Notify method has pointer receiver)

Detailed code: http: / / play golang. org/p/KG8 - Qb7gqM

Why doesn't the compiler consider what type our value is that implements the interface? The invocation rules of the interface are based on the recipients of these methods and how the interface is invoked. The following are the rules defined in the language specification to indicate whether a value of type 1 or pointer implements the interface:

1. The set of callable methods of type *T contains all sets of methods whose recipients are *T or T

What this rule says is that if the interface variable we use to call a particular interface method is a pointer type, then the recipient of the method can be either a value type or a pointer type. Obviously our example does not fit this rule, because the interface variable we pass in to the SendNotification function is of a value type.

1. The set of callable methods of type T contains all the methods whose recipients are T

What this rule says is that if the interface variable we use to call a particular interface method is a value type, then the recipient of the method must also be a value type for the method to be called. Obviously our example doesn't fit this rule either, because the recipient of our Notify method is a pointer type.

There are only these two rules in the language specification, and through these two rules, I get the rules that match our example:

1. The set of callable methods of type T does not contain methods whose receiver is *T

We happened to catch this rule that I inferred, so the compiler will report an error. The Notify method USES the pointer type as the receiver and we call it by the value type. The solution is also simple, we just pass in the address of the User value to the SendNotification function:


func main() {
  user := &User{
    Name:  "AriesDevil",
    Email: "ariesdevil@xxoo.com",
  }
 
  SendNotification(user)
} // Output:
User: Sending User Email To AriesDevil<ariesdevil@xxoo.com>

Detailed code: http: / / play golang. org/p/kEKzyTfLjA

Embedded type

Struct types can contain anonymous or embedded fields. Also called embedding a type. When we embed a type into the structure, the name of the type ACTS as the field name of the embedded field.

Let's define a new type and embed our User type:


type Admin struct {
  User
  Level  string
}

We've defined a new type, Admin, and we're going to embed the User type into it, and notice that this is not called inheritance, it's called composition. The User type is not related to the Admin type.

Let's change the main function 1, create a variable of type Admin and pass the address of the variable into the SendNotification function:


func main() {
  admin := &Admin{
    User: User{
      Name:  "AriesDevil",
      Email: "ariesdevil@xxoo.com",
    },
    Level: "master",
  }
 
  SendNotification(admin)
} // Output
User: Sending User Email To AriesDevil<ariesdevil@xxoo.com>

Detailed code: http: / / play golang. org/p/ivzzzk78TC

As it turns out, we can call the SendNotification function with one pointer of type Admin. Now the Admin type also implements this interface through method promotion from the embedded User type.

If the Admin type contains fields and methods of the User type, what are their relationships in the structure?

When we embed a type, a method of that type becomes a method of an external type, but when it is called, the recipient of the method is an internal type (the embedded type), not an external type. - Effective Go

So the name of the embedded type ACTS as the field name, and the embedded type exists as an internal type, so we can call the following method:


admin.User.Notify() // Output
User: Sending User Email To AriesDevil<ariesdevil@xxoo.com>

Detailed code: http: / / play golang. org p / 0 WL_5Q6mao

Here we access the fields and methods of the internal types by their type names. However, these fields and methods are also promoted to external types:


admin.Notify() // Output
User: Sending User Email To AriesDevil<ariesdevil@xxoo.com>

Detailed code: http: / / play golang. snaaJojRo org p / 2

So calling the Notify method through an external type is essentially a method of an internal type.

Here are the rules for promoting the set of internally-typed methods in the Go language:

Given 1 struct type S and 1 struct type T, method promotion is included in the set of struct methods as specified below:

1. If S contains an anonymous field T, both the S and *S method sets contain method promotion with the recipient T.

What this rule says is that when we embed a type, the method whose recipient is the value type of the embedded type will be promoted and can be called by the value and pointer of the external type.

1. The set of methods of type *S contains method promotion with recipient *T

This rule is when we embed a type, that can be invoked by external type of pointer method set only embedded type set of recipients for pointer type of method, that is to say, when an external type using a pointer called internal type of method, only the recipient for the interior of the pointer type type method set will be promoted.

1. If S contains an anonymous field *T, both the S and *S method sets contain method promotion with the recipient being T or *T

What this rule says is that when we embed a pointer of type 1, the method whose recipient of the embedded type is a value type or pointer type will be promoted and can be called by a value or pointer of an external type.

These are the only three rules of method promotion in the language specification, from which I derive one rule:

1. If S contains an anonymous field T, S's method set does not contain method promotion with the recipient being *T.

What this rule says is that when we embed a type, the method whose recipient is a pointer to the embedded type will not be accessible by the value of the external type. This is also due to the interface rule 1 we stated above.

Answer the first question

Now we can write a program to answer the first two questions. First, let's make the Admin type implement the Notifier interface:


func (a *Admin) Notify() error {
  log.Printf("Admin: Sending Admin Email To %s<%s>\n",
      a.Name,
      a.Email)
     
  return nil
}

The interface implemented by type Admin displays one piece of information about admin. When we use a pointer of type Admin to call the function SendNotification, this will help us determine which interface implementation is being called.

Now create a value of type Admin and pass its address into the SendNotification function to see what happens:


func main() {
  admin := &Admin{
    User: User{
      Name:  "AriesDevil",
      Email: "ariesdevil@xxoo.com",
    },
    Level: "master",
  }
 
  SendNotification(admin)
} // Output
Admin: Sending Admin Email To AriesDevil<ariesdevil@xxoo.com>

Detailed code: http: / / play golang. org/p/JGhFaJnGpS

As expected, the interface implementation of type Admin is called by the SendNotification function. Now what happens when we call the Notify method with an external type:


admin.Notify() // Output
Admin: Sending Admin Email To AriesDevil<ariesdevil@xxoo.com>

Detailed code: http: / / play golang. org/p/EGqK6DwBOi

We get the output of the interface implementation of type Admin. Interface implementations of type User are no longer promoted to external types.

Now we have enough evidence to answer the question:

1. Does the compiler report errors because we have two interface implementations at the same time?

No, because when we use embedded types, the type name ACTS as the field name. The embedded type contains its own fields and methods as an internal type of a structure, and has a 1-only name. So we can have an internal implementation and an external implementation of the same interface.

1. If the compiler accepts such a definition, how does the compiler determine which implementation to use when the interface is called?

If the external type contains a compliant interface implementation, it will be used. Otherwise, by method promotion, any interface implementation of an internal type can be used directly by an external type.

conclusion

In Go, methods, interfaces, and embedded types 1 work in a unique way. These features help us organize the structure like an object and then do the same thing, without all the other complications. With the language features discussed in this article, you can build an abstract and scalable framework with very little code.


Related articles: