Traditional operas

Recently, when writing Go code, you needed to customize a string conversion method for a struct

func (ms MyStruct) String(a) string
Copy the code

However, the implementation is torn between value methods and pointer methods.

The syntactically sweet nature of Go makes the invocation of the two approaches consistent, which left me struggling to decide which was better, so I decided to delve deeper into the principles behind it in order to write more idiomatic Go code in the future.

Author: Greenwood Birdwww.qtmuniao.comPlease indicate the source

The difference between

In the official Effective Go documentation, the difference is precisely described:

The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.

There is a handy exception, though. When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically.

The general idea is as follows:

  1. Value methods can be called with Pointers and values, but pointer methods can only be called with Pointers.
  2. One exception is if a value is addressable, orThe left value), the compiler automatically inserts the fetch when the value calls the pointer method, making it look like the pointer method can be called from the value in this case.

After reading this explanation, THERE is an MMP in my heart, which is not worth speaking improperly. Go syntactic candy is fun to use at first, but the more you use it, the more semantic complexity you find it introduces, which can be a great burden to your mind. Examples include Type assertion, automatic dereferencing, automatic insertion of address characters, automatic insertion of semicolons, etc. You can’t be too greedy. You can’t have both wants and wants.

Too much talk is easy to lie.

package main

import (
	"fmt"
)

type Foo struct {
	name string
}

func (f *Foo) PointerMethod(a) {
	fmt.Println("pointer method on", f.name)
}

func (f Foo) ValueMethod(a) {
	fmt.Println("value method on", f.name)
}

func NewFoo(a) Foo { // Return an rvalue
	return Foo{name: "right value struct"}}func main(a) {
	f1 := Foo{name: "value struct"}
  f1.PointerMethod() // The compiler automatically inserts the address to (&f1).pointermethod ().
	f1.ValueMethod()

	f2 := &Foo{name: "pointer struct"}
	f2.PointerMethod() 
	f2.ValueMethod() // The compiler will automatically dereference (*f2).pointermethod ()

	NewFoo().ValueMethod()
	NewFoo().PointerMethod() // Error!!!
}

Copy the code

The last sentence failed as follows:

./pointer_method.go:34:10: cannot call pointer method on NewFoo()
./pointer_method.go:34:10: cannot take the address of NewFoo()
Copy the code

It looks like the compiler first tried calling pointer method on the rvalue returned by NewFoo(). Then try to insert the address character, failed, can only report an error.

As for the difference between an lvalue and an rvalue, you can search for it yourself. In general, the most important difference is whether it can be addressed, and what can be addressed is an lvalue, which can appear on the left or the right of an assignment; Rvalues that cannot be addressed, such as function return values, literals, constant values, and so on, can only appear to the right of an assignment.

trade-offs

For a particular scenario, the tradeoff is equivalent to another problem: how you pass parameters — values or Pointers — when defining a function.

Take the above example:

func (f *Foo) PointerMethod(a) {
	fmt.Println("pointer method on ", f.name)
}

func (f Foo) ValueMethod(a) {
	fmt.Println("value method on", f.name)
}
Copy the code

Consider the following two functions:

func PointerMethod(f *Foo) {
	fmt.Println("pointer method on ", f.name)
}

func ValueMethod(f Foo)  {
	fmt.Println("value method on", f.name)
}
Copy the code

In Go terms, the function’s receiver is treated as an argument.

So what about passing values or Pointers? This is a kind of soul torture that almost every language encounters. Of course, Java first expressed dissatisfaction, not here, interested in their own Google.

There are several main considerations when defining receiver as a value or pointer:

  1. Method whether the receiver itself needs to be modified. If so, the receiver must be a pointer.
  2. Efficiency. If receiver is a value, copies of structs must be made during method calls, and large object copies are expensive.
  3. Consistency. Struct method (value method); pointer method (int method);

When do you use a value method? Using a value method for a very simple immutable object can lighten the load on the GC, and that seems to be the only benefit. So remember:

Use pointer method if you are not sure.

Of course, Go big people afraid you still have questions, so help you list some common cases in detail, please see here: github.com/golang/go/w…

reference

  1. The effective go:golang.org/doc/effecti…
  2. Golang faq:golang.org/doc/faq#met…
  3. Golang Code Review Comments: github.com/golang/go/w…
  4. Stackoverflow: stackoverflow.com/questions/2…

The Last Thing

Welcome to pay attention to my official account: “Distributed drip”, get more system articles.