1. Overview
exist"Commonly used syntax sugar in Golang》In this blog post, we explain the 12 commonly used grammatical sugars in Golang. In this article, we mainly explain the recipient's method of syntactic sugar.
Before introducing the syntax sugar of Golang receiver method, let’s briefly talk about the pointer of Go language (Pointer), which is roughly understood as follows:
- The & symbol before the variable name is the memory address of the variable, not the value;
- The * symbol before the data type means that the memory address of the corresponding data type is to be stored, not the value;
- The * symbol before the variable name represents the value taken from the memory address (Dereferencing).
Note 1: For details of golang pointer, please refer to "Golang pointer implicit indirect reference》This blog post.
2. Recipient method syntax sugar
In Go, for a custom type T, when defining a method for it, its receiver can be type T itself or a pointer *T of type T.
type Instance struct{} func (ins *Instance) Foo() string { return "" }
In the example above, when we define the Foo method of Instance, its receiver is a pointer type (*Instance).
func main() { var _ = Instance{}.Foo() //Compilation error: cannot call pointer method on Instance{} , the variable is immutable (the variable has no address and cannot be addressed)}
Therefore, if we call the Foo method with the Instance{} value of the Instance type itself, we will get the above error.
type Instance struct{} func (ins Instance) Foo() string { return "" } func main() { var _ = Instance{}.Foo() // Compile through}
At this point, if we change the recipient of the Foo method to the Instance type, there is no problem.
This shows that when defining a function method of type T, its receiver type determines what type of object can call the function method in the future. But, is this really the case?
type Instance struct{} func (ins *Instance) String() string { return "" } func main() { var ins Instance _ = () // The compiler will automatically obtain the address of ins and convert it into a pointer to type Instance_ = (&ins).String()}
In fact, even if the receiver of the Foo method is the pointer type, there is still no problem using the ins call above.
The Ins value belongs to the Instance type, not *Instance, but can call the Foo method. Why is this? This is actually the syntax sugar provided by the Go compiler!
When a variable is mutable (that is, the variable is a variable with an address that we can address), it is legal for us to directly call the *T method on a variable of type T because the Go compiler implicitly gets its address. Variable variable means that variables are addressable, so the Instance{}.Foo() mentioned above will get a compilation error because the Instance{} value cannot be addressable.
Note 1: In Go, even if the variable is not explicitly initialized, the compiler will still allocate memory space to it, so the variable still has a memory address. However, since variables are not initialized, they are assigned only to the default zero value of their type, rather than the initial value. Of course, these default values are also stored in the memory space allocated by the variable.
For example, the following code defines an integer variable x, which is not explicitly initialized, but still has an address when allocating memory:
var x int ("%p\n", &x) // Output variables x Memory address
The output result is similar to: 0xc0000120a0, indicating that the memory address of the variable x has been allocated. However, since the variable is not initialized, the value of x will be the default value of the integer 0.
3. In-depth testing
3.1 Example
package main type B struct { Id int } func New() B { return B{} } func New2() *B { return &B{} } func (b *B) Hello() { return } func (b B) World() { return } func main() { // The receiver of the method is of type *T New().Hello() // Compilation fails b1 := New() () // Compile through b2 := B{} () // Compile through (B{}).Hello() // Compilation fails B{}.Hello() // Compilation fails New2().Hello() // Compile through b3 := New2() () // Compile through b4 := &B{} // Compile through () // Compile through (&B{}).Hello() // Compile through // The receiver of the method is of type T New().World() // Compile through b5 := New() () // Compile through b6 := B{} () // Compile through (B{}).World() // Compile through B{}.World() // Compile through New2().World() // Compile through b7 := New2() () // Compile through b8 := &B{} // Compile through () // Compile through (&B{}).World() // Compile through}
Output result:
./:25:10: cannot call pointer method on New()
./:25:10: cannot take the address of New()
./:33:10: cannot call pointer method on B literal
./:33:10: cannot take the address of B literal
./:34:8: cannot call pointer method on B literal
./:34:8: cannot take the address of B literal
3.2 Summary of the problem
Assuming that the receiver has both T type and *T pointer type on the T type method, then the method of the *T receiver cannot be called on the unaddressable T value.
- &B{} is a pointer, addressable
- B{} is a value, not addressable
- b := B{} b is a variable, addressable
4. Summary
In Golang, when a variable is mutable (that is, the variable is a variable with an address, we can address it), we can perform operations on the variable by making a method call to the pointer of the variable, otherwise it will cause a compile error.
This is the introduction to this article about the detailed explanation of the use of syntax sugar for recipients in Golang. For more related content on Golang, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!