Methods
The difference between methods and functions in Go is that methods have receivers, while functions do not, and only custom types can have methods. Let's look at an example first.
type IntSlice []int
func (i IntSlice) Get(index int) int {
return i[index]
}
func (i IntSlice) Set(index, val int) {
i[index] = val
}
func (i IntSlice) Len() int {
return len(i)
}First, we declared a type IntSlice, whose underlying type is []int, and then declared three methods: Get, Set, and Len. The method looks almost the same as a function, except for the small part (i IntSlice). i is the receiver, and IntSlice is the type of the receiver. The receiver is similar to this or self in other languages, except that in Go it needs to be explicitly specified.
func main() {
var intSlice IntSlice
intSlice = []int{1, 2, 3, 4, 5}
fmt.Println(intSlice.Get(0))
intSlice.Set(0, 2)
fmt.Println(intSlice)
fmt.Println(intSlice.Len())
}Using a method is similar to calling a class's member method: first declare it, then initialize it, then call it.
Value Receiver
There are two types of receivers: value receiver and pointer receiver. Let's look at an example:
type MyInt int
func (i MyInt) Set(val int) {
i = MyInt(val) // Modified, but won't have any effect
}
func main() {
myInt := MyInt(1)
myInt.Set(2)
fmt.Println(myInt)
}After running the above code, you will find that myInt is still 1 and has not been changed to 2. When a method is called, the value of the receiver is passed into the method. In this example, the receiver is a value receiver, which can be simply regarded as a formal parameter. Modifying the value of a formal parameter does not affect the value outside the method. So what happens if we call it through a pointer?
func main() {
myInt := MyInt(1)
(&myInt).Set(2)
fmt.Println(myInt)
}Unfortunately, such code still cannot modify the internal value. To match the receiver's type, Go will dereference it, interpreting it as (*(&myInt)).Set(2).
Pointer Receiver
With a small modification, you can normally modify the value of myInt.
type MyInt int
func (i *MyInt) Set(val int) {
*i = MyInt(val)
}
func main() {
myInt := MyInt(1)
myInt.Set(2)
fmt.Println(myInt)
}Now the receiver is a pointer receiver. Although myInt is a value type, when calling a method with a pointer receiver through a value type, Go will interpret it as (&myint).Set(2). Therefore, when the method's receiver is a pointer, no matter whether the caller is a pointer or not, it can modify the internal value.
During parameter passing in functions, it is passed by value. If passing an integer, it copies that integer; if it's a slice, it copies that slice; but if it's a pointer, it only needs to copy this pointer. Obviously, passing a pointer consumes less resources than passing a slice. The same applies to receivers. In most cases, pointer receivers are recommended, but the two should not be mixed. Use either both or neither. Let's look at an example below.
TIP
You need to understand interfaces first
type Animal interface {
Run()
}
type Dog struct {
}
func (d *Dog) Run() {
fmt.Println("Run")
}
func main() {
var an Animal
an = Dog{}
// an = &Dog{} is the correct way
an.Run()
}This code will not compile. The compiler will output the following error:
cannot use Dog{} (value of type Dog) as type Animal in assignment:
Dog does not implement Animal (Run method has pointer receiver)In translation, it means you cannot use Dog{} to initialize a variable of type Animal because Dog does not implement Animal. There are two solutions: one is to change the pointer receiver to a value receiver, and the other is to change Dog{} to &Dog{}. Let me explain them one by one.
type Dog struct {
}
func (d Dog) Run() { // Changed to value receiver
fmt.Println("Run")
}
func main() { // Can run normally now
var an Animal
an = Dog{}
// an = &Dog{} also works
an.Run()
}In the original code, the receiver of the Run method is *Dog. Naturally, the one implementing the Animal interface is Dog pointer, not Dog struct. These are two different types, so the compiler considers Dog{} is not an implementation of Animal, and therefore cannot be assigned to variable an. The second solution is to assign the Dog pointer to variable an. However, when using a value receiver, Dog pointer can still be assigned to animal normally. This is because Go will dereference the pointer when appropriate because you can find the Dog struct through the pointer, but the reverse is not true: you cannot find the Dog pointer through the Dog struct. If you simply mix value receivers and pointer receivers in a struct, it's not a big deal, but after using them together with interfaces, errors will occur. It's better to either always use value receivers or always use pointer receivers to form a good convention and reduce the maintenance burden.
There is another case: when the value receiver is addressable, Go will automatically insert the pointer operator to make the call. For example, slices are addressable, so you can still modify their internal values through a value receiver. For example:
type Slice []int
func (s Slice) Set(i int, v int) {
s[i] = v
}
func main() {
s := make(Slice, 1)
s.Set(0, 1)
fmt.Println(s)
}Output:
[1]But this brings up another problem: if you add elements to it, the situation is different. Let's look at the example below:
type Slice []int
func (s Slice) Set(i int, v int) {
s[i] = v
}
func (s Slice) Append(a int) {
s = append(s, a)
}
func main() {
s := make(Slice, 1, 2)
s.Set(0, 1)
s.Append(2)
fmt.Println(s)
}[1]The output is still the same as before. The append function has a return value. After adding elements to a slice, you must reassign the slice, especially after expansion. Modifying a value receiver inside a method does not have any effect, which leads to the result in the example. Changing to a pointer receiver works normally.
type Slice []int
func (s *Slice) Set(i int, v int) {
(*s)[i] = v
}
func (s *Slice) Append(a int) {
*s = append(*s, a)
}
func main() {
s := make(Slice, 1, 2)
s.Set(0, 1)
s.Append(2)
fmt.Println(s)
}Output:
[1 2]